> ## 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.

# Following your profit and loss

> Read your realized profit and loss for a period, break it down by symbol and by day, and reconcile it with your closed orders.

This guide shows how a dashboard or an accounting job follows your **realized** profit and loss through the API: the total for a period, the split by metal and symbol, a daily series for a chart, and a reconciliation against the closed orders themselves. The calls are described in full on [Profit & loss](/api-reference/pnl/overview).

> [!NOTE]
>
> Profit and loss is **realized only**: it comes from orders Bullionwale has closed. Open positions are not valued, so there is no unrealized or mark-to-market figure to follow. On the **test** path every figure is zero, because test orders are never closed; the examples below use the production path and a live key, which can read even while production trading is switched off.

You need a key with `orders:read`. The examples assume it is in the `BULLIONWALE_API_KEY` environment variable.

## How a profit or loss is realized

1. You buy or sell with `POST /orders` (or in the app). The order is `open`: it has no profit or loss yet.
2. Bullionwale closes the order. Its `status` becomes `closed`, and `close_price`, `realized_pnl` and `closed_at` are set (see [the order object](/api-reference/orders/overview#the-order-object)).
3. From then on, the order's `realized_pnl` counts in `GET /pnl` on the India Standard Time (IST) day of its `closed_at`.

The API does not close orders. A figure for "today" grows as Bullionwale closes orders during the day.

## 1. The total for a period

Ask for a range of IST days, both included:

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

```python Python theme={null}
import os
from decimal import Decimal

import requests

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

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

```json 200 OK theme={null}
{
  "environment": "production",
  "from": "2026-10-01",
  "to": "2026-10-31",
  "realized_pnl": "-2620.51",
  "closed_orders": 5,
  "by_metal": {"gold": "-3620.50", "silver": "999.99"},
  "symbols": [
    {"symbol_code": "GOLD_DEP_TRADE_999", "metal": "gold", "realized_pnl": "-4000.00", "closed_orders": 2},
    {"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}
  ]
}
```

* A negative amount is a loss: here October closed at a net loss of ₹2,620.51.
* Leave `from` out to start at your first closed order, and `to` out to run to now. Leave both out for all time.

## 2. By metal and by symbol

The same answer carries the breakdown, so no second call is needed:

* `by_metal` always has both `gold` and `silver`, `"0.00"` when nothing closed.
* `symbols` lists only symbols with a closed order in the range, sorted by `symbol_code`.

The parts add up to the total. Check it with decimals, never floats:

```python theme={null}
from decimal import Decimal

pnl = {"realized_pnl": "-2620.51",
       "by_metal": {"gold": "-3620.50", "silver": "999.99"},
       "symbols": [{"realized_pnl": "-4000.00"}, {"realized_pnl": "379.50"},
                   {"realized_pnl": "999.99"}]}

total = Decimal(pnl["realized_pnl"])
assert sum(Decimal(v) for v in pnl["by_metal"].values()) == total
assert sum(Decimal(s["realized_pnl"]) for s in pnl["symbols"]) == total
```

## 3. A daily series for a chart

`GET /pnl/daily` returns every day of the range, oldest first, with `"0.00"` on days when nothing closed, so it can be charted as it is:

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

```python Python theme={null}
import os
from decimal import Decimal

import requests

BASE = "https://api.bullionwale.com/api/trading/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()
running = Decimal("0")
for day in daily["days"]:
    running += Decimal(day["realized_pnl"])
    print(day["date"], day["realized_pnl"], "cumulative", running)
```

```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}
  ]
}
```

The cumulative line here runs 0.00, 1.00, 11.00, and ends at the range's `realized_pnl`.

* Without `from` and `to`, you get the last 30 IST days up to today.
* One request covers at most 366 days; a longer range is `422 invalid_request`. For a longer history, ask for one year at a time and join the `days`.
* Days are IST calendar days: an order closed at 23:45 IST on 20 October counts on `2026-10-20`, although it is already 21 October in some other timezones.

## 4. Reconcile with your closed orders

To prove the figures, or to keep your own ledger, rebuild them from the orders. Each closed order carries its own `realized_pnl` and `closed_at`:

1. List your closed orders with `GET /orders?status=closed`, 200 at a time, using `offset` to page.
2. Keep the orders with a `realized_pnl`, and whose `status_detail` is `closed` or `sell_closed`. A closed order whose metal was later delivered shows `status` `closed` too, with `status_detail` `delivered`, and is not counted in `GET /pnl`, by design: the API applies the same P\&L rules as the Bullionwale web and mobile apps.
3. Put each order on the IST day of its `closed_at` (or of `created_at` if `closed_at` is `null`, which only very old orders have).
4. Sum per day with decimals, and compare with `GET /pnl/daily`.

`since` on `GET /orders` filters on when an order was **created**, not when it closed. An order created last month and closed this month still belongs to this month's P\&L, so do not narrow the order list with `since` when you reconcile by closing day.

```python Python theme={null}
import os
from collections import defaultdict
from datetime import date, datetime
from decimal import Decimal
from zoneinfo import ZoneInfo

import requests

BASE = "https://api.bullionwale.com/api/trading/v1"
HEADERS = {"X-API-Key": os.environ["BULLIONWALE_API_KEY"]}
IST = ZoneInfo("Asia/Kolkata")
FROM, TO = date(2026, 10, 19), date(2026, 10, 21)

# 1-3. Every closed order with a realized P&L, by the IST day it closed.
by_day: dict[date, Decimal] = defaultdict(Decimal)
offset = 0
while True:
    page = requests.get(f"{BASE}/orders",
                        params={"status": "closed", "limit": 200, "offset": offset},
                        headers=HEADERS, timeout=10).json()
    for o in page["items"]:
        if o["realized_pnl"] is None or o["status_detail"] not in ("closed", "sell_closed"):
            continue
        day = datetime.fromisoformat(o["closed_at"] or o["created_at"]).astimezone(IST).date()
        if FROM <= day <= TO:
            by_day[day] += Decimal(o["realized_pnl"])
    offset += len(page["items"])
    if not page["items"] or offset >= page["total"]:
        break

# 4. Compare with the API's own daily figures.
daily = requests.get(f"{BASE}/pnl/daily",
                     params={"from": FROM.isoformat(), "to": TO.isoformat()},
                     headers=HEADERS, timeout=10).json()
for d in daily["days"]:
    mine = by_day.get(date.fromisoformat(d["date"]), Decimal("0"))
    status = "ok" if mine == Decimal(d["realized_pnl"]) else "DIFFERENT"
    print(d["date"], d["realized_pnl"], mine, status)
```

The two always agree for days that are over. For **today** they can differ for a moment if Bullionwale closes an order between your two calls: run it again.

## What test returns

The test path answers both calls in the production shape with zeros: `realized_pnl` `"0.00"`, `closed_orders` `0`, `by_metal` zeros, an empty `symbols`, and every day of the range at `"0.00"`. The parameters are checked the same way, so a `422` on test is a `422` on production. Use test to build your parsing, paging and charting; use production reads to see real figures.

## Keeping it current

* **Poll gently.** Realized P\&L changes only when Bullionwale closes an order. Reading `GET /pnl` for today every few minutes is plenty, and well within the read budget (see [Rate limits](/rate-limits)).
* **End of day.** Read the finished day after the market closes (23:30 IST; see [Market hours](/market-hours)), and store it: a past day's figure changes only if Bullionwale corrects an order.
* **Webhooks**, once they are turned on, will send `order.closed` when an order closes, with its `realized_pnl` (see the [Event catalog](/webhooks/event-catalog)). Until then, poll.


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