> For the complete documentation index, see [llms.txt](https://docs.openalgo.in/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openalgo.in/developers/design-documentation/44-pnl-tracker.md).

# 44 - PnL Tracker

## Overview

The PnL (Profit & Loss) Tracker provides real-time intraday P\&L monitoring by combining tradebook data with historical price data. It calculates mark-to-market (MTM) P\&L for all positions throughout the trading day and displays it via interactive charts.

## Architecture Diagram

<figure><img src="https://17901342-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmBwEhITzgv0O0fEGIIRN%2Fuploads%2Fgit-blob-26ed9741c85c4887e44b45583962d2583f59a3a0%2Fdiagram-pnl-tracker-architecture.png?alt=media" alt="PnL tracker architecture: React page posts to /pnltracker/api/pnl, which reads tradebook, positionbook and 1-minute history services and builds position-window MTM, portfolio P&#x26;L and drawdown"><figcaption></figcaption></figure>

## Implementation Details

### Position Window Tracking

The PnL tracker creates "position windows" to track when positions were opened and closed:

```python
# Data structure for each position window
position_window = {
    "start_time": datetime,    # When position was opened
    "end_time": datetime,      # When position was closed (None if still open)
    "qty": float,              # Position quantity
    "price": float,            # Entry price
    "action": str,             # "BUY" or "SELL"
    "exit_price": float        # Exit price (None if still open)
}
```

### Trade Timestamp Parsing

The system handles multiple timestamp formats from different brokers:

```python
# Supported formats in parse_trade_timestamp():
formats = [
    "%d-%b-%Y %H:%M:%S",    # AngelOne: "17-Dec-2025 10:54:03"
    "%H:%M:%S %d-%m-%Y",    # Flattrade: "09:41:01 17-12-2025"
    "%d-%m-%Y %H:%M:%S",    # "17-12-2025 09:41:01"
    "%Y-%m-%d %H:%M:%S",    # ISO-like: "2025-12-17 10:30:00"
    "%Y-%m-%dT%H:%M:%S",    # ISO: "2025-12-17T10:30:00"
]
```

### Rate Limiting

Historical data API calls are rate-limited to avoid broker rate limits:

```python
class RateLimiter:
    """Thread-safe rate limiter for API calls"""

    def __init__(self, calls_per_second=2):
        self.calls_per_second = calls_per_second
        self.min_interval = 1.0 / calls_per_second
        self.last_call_time = 0
        self.lock = threading.Lock()

    def wait(self):
        """Wait if necessary to respect rate limit"""
        with self.lock:
            current_time = time_module.time()
            elapsed = current_time - self.last_call_time
            if elapsed < self.min_interval:
                sleep_time = self.min_interval - elapsed
                time_module.sleep(sleep_time)
            self.last_call_time = time_module.time()

# Global instance - 2 calls per second (conservative)
history_rate_limiter = RateLimiter(calls_per_second=2)
```

### Carry-Forward Position PnL Tracking

The PnL tracker handles carry-forward positions, meaning positions opened on previous days that are still open today. This is critical for NRML/CNC positions that span multiple trading sessions.

```python
# Carry-forward position handling:
# 1. Detect open positions not in today's tradebook (no entry trade today)
# 2. Fetch historical data from market open to track MTM
# 3. Calculate P&L relative to previous close price
# 4. Two cases handled:
#    Case 1: Open carry-forward position (still held, no today trades)
#    Case 2: Closed carry-forward position (exit-only trades today)
```

**Position Detection:**

* Compares current positions (from positionbook) against today's trades
* Positions with quantities but no matching entry trades are carry-forward
* Historical data fetched from 9:15 AM to calculate intraday P\&L movement

**P\&L Calculation for Carry-Forward:**

* Uses previous day's close price as the reference
* Tracks MTM from market open using 1-minute historical bars
* Merges carry-forward PnL series with regular trade PnL series

## API Endpoint

### Get P\&L Data

```
POST /pnltracker/api/pnl
Content-Type: application/json
Cookie: session=...
```

**Response:**

```json
{
    "status": "success",
    "data": {
        "current_mtm": 3750.00,
        "max_mtm": 4200.00,
        "max_mtm_time": "11:30",
        "min_mtm": 1000.00,
        "min_mtm_time": "09:45",
        "max_drawdown": -800.00,
        "pnl_series": [
            {"time": 1706165700000, "value": 1000.00},
            {"time": 1706165760000, "value": 1500.00},
            {"time": 1706165820000, "value": 2200.00}
        ],
        "drawdown_series": [
            {"time": 1706165700000, "value": 0.00},
            {"time": 1706165760000, "value": -200.00},
            {"time": 1706165820000, "value": 0.00}
        ]
    }
}
```

### Response Fields

| Field             | Type   | Description                            |
| ----------------- | ------ | -------------------------------------- |
| `current_mtm`     | number | Current mark-to-market P\&L            |
| `max_mtm`         | number | Maximum P\&L reached during the day    |
| `max_mtm_time`    | string | Time when max P\&L was reached (HH:MM) |
| `min_mtm`         | number | Minimum P\&L during the day            |
| `min_mtm_time`    | string | Time when min P\&L was reached (HH:MM) |
| `max_drawdown`    | number | Largest drawdown from peak (negative)  |
| `pnl_series`      | array  | Time series data for P\&L chart        |
| `drawdown_series` | array  | Time series data for drawdown chart    |

## Calculation Flow

<figure><img src="https://17901342-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmBwEhITzgv0O0fEGIIRN%2Fuploads%2Fgit-blob-2634a467133ee2116a31685f6a0c59b56cf20d5e%2Fdiagram-pnl-tracker-calculation-flow.png?alt=media" alt="PnL calculation flow from session and auth checks through tradebook, positions, per-symbol rate-limited history, carry-forward handling, zero fill and drawdown to the JSON response"><figcaption></figcaption></figure>

## Data Dependencies

The PnL tracker relies on these services (no dedicated database):

| Service                            | Purpose                         |
| ---------------------------------- | ------------------------------- |
| `services/tradebook_service.py`    | Get today's executed trades     |
| `services/positionbook_service.py` | Get current positions           |
| `services/history_service.py`      | Get 1-minute historical bars    |
| `database/auth_db.py`              | Get user auth token and API key |

## Frontend Components

### React Page

**Location:** `frontend/src/pages/PnLTracker.tsx`

The React frontend:

* Polls `/pnltracker/api/pnl` periodically
* Renders metrics cards (current MTM, max, min, drawdown)
* Uses LightWeight Charts for interactive P\&L visualization
* Shows separate drawdown chart below main chart

### Legacy Compatibility Route

`/pnltracker/legacy` is still registered, but the referenced `templates/pnltracker.html` file is absent from the current tree. Treat this route as unavailable until the route is removed or a template is restored. The supported UI is the React `/pnl-tracker` page, served by `blueprints/react_app.py`. `blueprints/pnltracker.py` also registers `/test_chart`, which renders the equally absent `templates/test_chart.html`.

## Edge Cases Handled

### Sub-Minute Trades

When a position is opened and closed within the same minute (no historical data points):

```python
# Calculate realized PnL even without historical data
if is_closed_position:
    if window["action"] == "BUY":
        realized = (window["exit_price"] - window["price"]) * window["qty"]
    else:  # SELL
        realized = (window["price"] - window["exit_price"]) * window["qty"]
```

### Pre-Trade Period

Zero P\&L data is added from market open (9:15 AM IST) to first trade time for complete visualization.

### Timezone Handling

All timestamps are converted to IST (Asia/Kolkata) timezone:

```python
ist = pytz.timezone("Asia/Kolkata")
if df["datetime"].dt.tz is None:
    df["datetime"] = df["datetime"].dt.tz_localize("UTC").dt.tz_convert(ist)
```

## Drawdown Calculation

```python
# Drawdown = Current P&L - Peak P&L (running maximum)
portfolio_pnl["Peak"] = portfolio_pnl["Total_PnL"].cummax()
portfolio_pnl["Drawdown"] = portfolio_pnl["Total_PnL"] - portfolio_pnl["Peak"]

# Max drawdown is the minimum value (most negative)
max_drawdown = portfolio_pnl["Drawdown"].min()
```

## Key Files Reference

| File                                | Purpose                               |
| ----------------------------------- | ------------------------------------- |
| `blueprints/pnltracker.py`          | Blueprint with P\&L calculation logic |
| `services/tradebook_service.py`     | Fetches tradebook from broker         |
| `services/positionbook_service.py`  | Fetches current positions             |
| `services/history_service.py`       | Fetches historical price data         |
| `frontend/src/pages/PnLTracker.tsx` | React UI component                    |
