Positions
Position endpoints let you query currently open positions, close an entire position, or partially close a position. Positions are created automatically when orders are filled and represent your current exposure in a given instrument.
Position fields
| Field | Type | Description |
|---|---|---|
id | number | Position index within the response (1-based) |
accountId | number | Account ID |
contractId | string | Contract ID for the position |
contractDisplayName | string | Full instrument name (e.g., "E-Mini NASDAQ 100 Futures") |
creationTimestamp | string | ISO 8601 timestamp (time of the query) |
type | number | Position direction: 1 = Long, 2 = Short |
size | number | Number of contracts currently held |
averagePrice | number | Average entry price across all fills |
POST Position/searchOpen
Get all currently open positions for an account.
URL: POST /api/topstep-sim/Position/searchOpen
Authentication: Bearer token required.
Request body
{ "accountId": 12345}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | Account ID |
Response
{ "success": true, "errorCode": 0, "errorMessage": null, "positions": [ { "id": 1, "accountId": 12345, "contractId": "CON.F.US.ENQ.H25", "contractDisplayName": "E-Mini NASDAQ 100 Futures", "creationTimestamp": "2025-01-15T14:30:05.000Z", "type": 1, "size": 2, "averagePrice": 21503.50 } ]}| Field | Type | Description |
|---|---|---|
positions | array | List of open position objects. Empty array [] if no positions are open. |
Example
result = api("Position/searchOpen", {"accountId": ACCOUNT_ID})
positions = result.get("positions", [])if positions: for pos in positions: direction = "LONG" if pos["type"] == 1 else "SHORT" print(f"{direction} {pos['size']} @ {pos['averagePrice']:.2f}")else: print("No open positions")curl -X POST https://api.test-max.com/api/topstep-sim/Position/searchOpen \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{"accountId": 12345}'POST Position/closeContract
Close an entire position in a given contract. This is equivalent to placing a market order in the opposite direction for the full position size.
URL: POST /api/topstep-sim/Position/closeContract
Authentication: Bearer token required.
Request body
{ "accountId": 12345, "contractId": "CON.F.US.ENQ.H25"}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | Account ID |
contractId | string | Yes | Contract ID of the position to close |
Response
{ "success": true, "errorCode": 0, "errorMessage": null}Example
# Close the entire NQ positionresult = api("Position/closeContract", { "accountId": ACCOUNT_ID, "contractId": CONTRACT_ID})
if result["success"]: print("Position closed")else: print(f"Close failed: {result['errorMessage']}")curl -X POST https://api.test-max.com/api/topstep-sim/Position/closeContract \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{"accountId": 12345, "contractId": "CON.F.US.ENQ.H25"}'Error responses
| Scenario | errorCode | errorMessage |
|---|---|---|
Invalid or unowned accountId | 1 | "Account not found" |
| No open positions | 2 | "Position not found" |
| Challenge no longer active | 4 | "Account violation" |
| Closing order rejected by the engine | 5 | The rejection reason |
POST Position/partialCloseContract
Partially close a position by specifying the number of contracts to close. Useful for scaling out of a trade.
URL: POST /api/topstep-sim/Position/partialCloseContract
Authentication: Bearer token required.
Request body
{ "accountId": 12345, "contractId": "CON.F.US.ENQ.H25", "size": 1}| Field | Type | Required | Description |
|---|---|---|---|
accountId | number | Yes | Account ID |
contractId | string | Yes | Contract ID of the position to partially close |
size | number | Yes | Number of contracts to close (must be less than or equal to the current position size) |
Response
{ "success": true, "errorCode": 0, "errorMessage": null}Example
# Scale out: close 1 of 3 contracts at the first targetpositions = get_positions(ACCOUNT_ID)for pos in positions: if pos["size"] > 1 and current_price >= first_target: result = api("Position/partialCloseContract", { "accountId": ACCOUNT_ID, "contractId": pos["contractId"], "size": 1 # Close 1 contract, keep the rest running }) if result["success"]: print(f"[INFO] Scaled out 1 contract at {current_price}")Error responses
| Scenario | errorCode | errorMessage |
|---|---|---|
Invalid or unowned accountId | 1 | "Account not found" |
| No open positions | 2 | "Position not found" |
| Challenge no longer active | 4 | "Account violation" |
| Size missing, non-integer, ≤ 0, or larger than the position | 5 | "Invalid close size" |
| Closing order rejected by the engine | 6 | The rejection reason |
Common patterns
Flatten all positions
Close every open position across all contracts:
result = api("Position/searchOpen", {"accountId": ACCOUNT_ID})
for pos in result.get("positions", []): api("Position/closeContract", { "accountId": ACCOUNT_ID, "contractId": pos["contractId"] })
print("All positions flattened")Scale-out strategy
Exit in stages to lock in profit while letting the remaining position run:
# Entry: buy 3 contractsapi("Order/place", { "accountId": ACCOUNT_ID, "contractId": CONTRACT_ID, "type": 2, "side": 0, "size": 3})
# In your main loop:positions = get_positions(ACCOUNT_ID)if positions: pos = positions[0] pnl_ticks = (bar["c"] - pos["averagePrice"]) / TICK_SIZE
# Take 1/3 off at +10 ticks if pnl_ticks >= 10 and pos["size"] == 3: api("Position/partialCloseContract", { "accountId": ACCOUNT_ID, "contractId": CONTRACT_ID, "size": 1 })
# Take another 1/3 off at +20 ticks elif pnl_ticks >= 20 and pos["size"] == 2: api("Position/partialCloseContract", { "accountId": ACCOUNT_ID, "contractId": CONTRACT_ID, "size": 1 })
# Let the last contract run with a trailing stopCheck if currently in a position
positions = get_positions(ACCOUNT_ID)is_long = any(p["type"] == 1 for p in positions)is_short = any(p["type"] == 2 for p in positions)is_flat = len(positions) == 0