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

# Profit & loss

> Realized profit and loss from your closed orders, in total, per symbol and per day.

Profit and loss here is **realized** only: the profit or loss Bullionwale recorded on each of your orders when it closed the order. Open positions are not valued, so there is no unrealized or mark-to-market figure anywhere in the API. For a walkthrough with reconciliation, see [Following your profit and loss](/following-pnl).

| Call | Endpoint | Scope |
| - | - | - |
| [Realized P\&L](#read-your-pl) | `GET /pnl` | `orders:read` |
| [Daily realized P\&L](#read-it-per-day) | `GET /pnl/daily` | `orders:read` |

| Environment | Total and per symbol | Per day |
| - | - | - |
| Test | `/api/trading/test/v1/pnl` | `/api/trading/test/v1/pnl/daily` |
| Production | `/api/trading/v1/pnl` | `/api/trading/v1/pnl/daily` |

## What is counted

* Every order of yours that Bullionwale has **closed** and recorded a `realized_pnl` for. Open, pending, rejected and deleted orders are not counted, and neither is a closed order whose metal was later delivered (it reads `status` `closed`, `status_detail` `delivered`). These are the same rules as the profit and loss in the Bullionwale web and mobile apps, so the figures match.
* Each order counts on the **India Standard Time (IST) calendar day it closed** (its `closed_at`). An old order without a closing time counts on the day it was created.
* An order's `realized_pnl` is the same value as on [`GET /orders/{order_id}`](/api-reference/orders/overview#the-order-object), so the figures here are sums you can reproduce.
* Amounts are rupees as decimal strings with 2 decimals. A negative amount is a loss.

## Query parameters

| Parameter | Calls | Meaning |
| - | - | - |
| `from` | both | The first IST day included, `YYYY-MM-DD`. |
| `to` | both | The last IST day included, `YYYY-MM-DD`. |

* **`GET /pnl`:** either may be left out. Without `from` the range starts at your first closed order; without `to` it runs to now. Without both, it is all time.
* **`GET /pnl/daily`:** `to` defaults to today (IST) and `from` to 29 days before `to`, so the default is the last 30 days. The range may cover at most 366 days.
* `from` after `to`, a range longer than 366 days on `/pnl/daily`, or a date that is not `YYYY-MM-DD` is refused with `422 invalid_request`.

## The P\&L object

`GET /pnl` returns:

| Field | Type | Meaning |
| - | - | - |
| `environment` | string | `test` or `production`. |
| `from`, `to` | string or null | The IST days included, as you sent them; `null` = open. |
| `realized_pnl` | string | Total realized P\&L in rupees, 2 decimals. |
| `closed_orders` | integer | How many closed orders the total covers. |
| `by_metal` | object | Realized P\&L per metal: always both `gold` and `silver`, `"0.00"` when none. |
| `symbols` | array | One entry per symbol with a closed order in the range, sorted by `symbol_code`: `symbol_code`, `metal`, `realized_pnl`, `closed_orders`. Empty when nothing closed. |

`GET /pnl/daily` returns:

| Field | Type | Meaning |
| - | - | - |
| `environment` | string | `test` or `production`. |
| `from`, `to` | string | The IST days covered, after the defaults are applied. |
| `realized_pnl` | string | The total over the range. |
| `days` | array | **Every** day of the range, oldest first, each with `date` (`YYYY-MM-DD`), `realized_pnl` (`"0.00"` when nothing closed) and `closed_orders`. |

`realized_pnl` on both calls equals the sum of its parts: of `by_metal`, of `symbols`, and of `days`.

## Read your P\&L

```bash curl theme={null}
curl -s "https://api.bullionwale.com/api/trading/test/v1/pnl?from=2026-10-01&to=2026-10-21" \
  -H "X-API-Key: $BULLIONWALE_API_KEY"
```

```python Python theme={null}
import os
import requests

BASE = "https://api.bullionwale.com/api/trading/test/v1"
HEADERS = {"X-API-Key": os.environ["BULLIONWALE_API_KEY"]}

pnl = requests.get(f"{BASE}/pnl", params={"from": "2026-10-01", "to": "2026-10-21"},
                   headers=HEADERS, timeout=10).json()
print(pnl["realized_pnl"], pnl["closed_orders"])
```

On test it is always zero, in the production shape:

```json 200 OK theme={null}
{
  "environment": "test",
  "from": "2026-10-01",
  "to": "2026-10-21",
  "realized_pnl": "0.00",
  "closed_orders": 0,
  "by_metal": {"gold": "0.00", "silver": "0.00"},
  "symbols": []
}
```

On production, with three closed orders in the range:

```json 200 OK theme={null}
{
  "environment": "production",
  "from": "2026-10-01",
  "to": "2026-10-21",
  "realized_pnl": "1379.49",
  "closed_orders": 3,
  "by_metal": {"gold": "379.50", "silver": "999.99"},
  "symbols": [
    {"symbol_code": "GOLD_TRADE_999", "metal": "gold", "realized_pnl": "379.50", "closed_orders": 2},
    {"symbol_code": "SILVER_999", "metal": "silver", "realized_pnl": "999.99", "closed_orders": 1}
  ]
}
```

379.50 + 999.99 = 1,379.49: the total is the sum of the metals, and of the symbols.

## Read it per day

```bash curl theme={null}
curl -s "https://api.bullionwale.com/api/trading/test/v1/pnl/daily?from=2026-10-19&to=2026-10-21" \
  -H "X-API-Key: $BULLIONWALE_API_KEY"
```

```python Python theme={null}
import os
import requests

BASE = "https://api.bullionwale.com/api/trading/test/v1"
HEADERS = {"X-API-Key": os.environ["BULLIONWALE_API_KEY"]}

daily = requests.get(f"{BASE}/pnl/daily", params={"from": "2026-10-19", "to": "2026-10-21"},
                     headers=HEADERS, timeout=10).json()
for day in daily["days"]:
    print(day["date"], day["realized_pnl"], day["closed_orders"])
```

```json 200 OK theme={null}
{
  "environment": "test",
  "from": "2026-10-19",
  "to": "2026-10-21",
  "realized_pnl": "0.00",
  "days": [
    {"date": "2026-10-19", "realized_pnl": "0.00", "closed_orders": 0},
    {"date": "2026-10-20", "realized_pnl": "0.00", "closed_orders": 0},
    {"date": "2026-10-21", "realized_pnl": "0.00", "closed_orders": 0}
  ]
}
```

On production:

```json 200 OK theme={null}
{
  "environment": "production",
  "from": "2026-10-19",
  "to": "2026-10-21",
  "realized_pnl": "11.00",
  "days": [
    {"date": "2026-10-19", "realized_pnl": "0.00", "closed_orders": 0},
    {"date": "2026-10-20", "realized_pnl": "1.00", "closed_orders": 1},
    {"date": "2026-10-21", "realized_pnl": "10.00", "closed_orders": 1}
  ]
}
```

Days with nothing closed are listed with `"0.00"`, so the series is ready to chart without filling gaps.

A range that is too long:

```json 422 Unprocessable Entity theme={null}
{
  "detail": {
    "code": "invalid_request",
    "message": "The range may cover at most 366 days."
  }
}
```

## Test and production

**Test is always zero.** Test orders are simulated fills that are never closed, so they never realize a profit or loss. The test calls return zero in the production shape, with every day listed on `/pnl/daily`, so your code can be written and run against them. The same parameters are checked, so a `422` on test is a `422` on production.

## Errors

| Code | Status | Meaning |
| - | - | - |
| `invalid_request` | 422 | `from` is after `to`, the daily range is longer than 366 days, or a date is not `YYYY-MM-DD`. |
| `insufficient_scope` | 403 | The key does not have `orders:read`. |
| `rate_limited` | 429 | The key's read budget is used up. Wait `Retry-After` seconds. |

Every code is in the [Error reference](/api-reference/error-reference).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.