Skip to content

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

┌──────────────────────────────────────────────────────────────────────────────┐
│                          PnL Tracker Architecture                            │
└──────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────┐
│                                 Data Sources                                 │
│                                                                              │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐               │
│  │  Tradebook      │  │  Position Book  │  │  History API    │               │
│  │  (Broker API)   │  │  (Broker API)   │  │  (1-minute bars)│               │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘               │
│           │                    │                    │                        │
│           └────────────────────┼────────────────────┘                        │
│                                │                                             │
│                                ▼                                             │
└──────────────────────────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────────────┐
│                  PnL Calculation (blueprints/pnltracker.py)                  │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐     │
│  │                    Position Window Tracking                          │    │
│  │                                                                      │    │
│  │  1. Parse trades from tradebook                                     │     │
│  │  2. Group by symbol/exchange                                        │     │
│  │  3. Create position windows (start_time, end_time, qty, price)      │     │
│  │  4. Apply rate limiting (2 calls/sec for history API)               │     │
│  │  5. Calculate MTM using historical close prices                     │     │
│  │  6. Aggregate all symbols into portfolio P&L                        │     │
│  └─────────────────────────────────────────────────────────────────────┘     │
│                                    │                                         │
│                                    ▼                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐     │
│  │                    P&L Calculation Formula                           │    │
│  │                                                                      │    │
│  │  For LONG positions:                                                 │    │
│  │    MTM P&L = (Current Price - Entry Price) × Quantity               │     │
│  │                                                                      │    │
│  │  For SHORT positions:                                                │    │
│  │    MTM P&L = (Entry Price - Current Price) × Quantity               │     │
│  │                                                                      │    │
│  │  Realized P&L = (Exit Price - Entry Price) × Quantity  [Long]       │     │
│  │                = (Entry Price - Exit Price) × Quantity  [Short]     │     │
│  └─────────────────────────────────────────────────────────────────────┘     │
└──────────────────────────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────────────┐
│                               Frontend Display                               │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐     │
│  │  Metrics Cards                                                       │    │
│  │                                                                      │    │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐              │       │
│  │  │ Current  │ │   Max    │ │   Min    │ │   Max    │              │       │
│  │  │   MTM    │ │   MTM    │ │   MTM    │ │ Drawdown │              │       │
│  │  │ +₹3,750  │ │ +₹4,200  │ │ +₹1,000  │ │  -₹800   │              │       │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘              │       │
│  └─────────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐     │
│  │  P&L Chart (LightWeight Charts)                                     │     │
│  │                                                                      │    │
│  │       ₹                                                              │    │
│  │    4000│        ╭──────╮                                            │     │
│  │    3000│    ╭───╯      ╰──╮                                         │     │
│  │    2000│╭───╯              ╰──────                                  │     │
│  │    1000│                                                            │     │
│  │       0├────────────────────────────► Time                          │     │
│  │        9:15  10:00  11:00  12:00  1:00                              │     │
│  └─────────────────────────────────────────────────────────────────────┘     │
└──────────────────────────────────────────────────────────────────────────────┘

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

FieldTypeDescription
current_mtmnumberCurrent mark-to-market P&L
max_mtmnumberMaximum P&L reached during the day
max_mtm_timestringTime when max P&L was reached (HH:MM)
min_mtmnumberMinimum P&L during the day
min_mtm_timestringTime when min P&L was reached (HH:MM)
max_drawdownnumberLargest drawdown from peak (negative)
pnl_seriesarrayTime series data for P&L chart
drawdown_seriesarrayTime series data for drawdown chart

Calculation Flow

┌──────────────────────────────────────────────────────────────────┐
│                       P&L Calculation Flow                       │
└──────────────────────────────────────────────────────────────────┘

Request arrives at /pnltracker/api/pnl


┌─────────────────────────┐
│ Get broker from session │
└───────────┬─────────────┘


┌─────────────────────────┐     ┌───────────────────────┐
│ Get tradebook via       │────▶│ services/tradebook    │
│ get_tradebook(api_key)  │     │ _service.py           │
└───────────┬─────────────┘     └───────────────────────┘


┌─────────────────────────┐     ┌───────────────────────┐
│ Get positions via       │────▶│ services/positionbook │
│ get_positionbook()      │     │ _service.py           │
└───────────┬─────────────┘     └───────────────────────┘


┌─────────────────────────┐
│ Group trades by symbol  │
│ Create position windows │
└───────────┬─────────────┘


┌─────────────────────────┐
│ For each symbol:        │
│ 1. Rate limit wait      │
│ 2. Get 1m history       │
│ 3. Calculate MTM        │
│ 4. Track realized P&L   │
└───────────┬─────────────┘


┌─────────────────────────┐
│ Aggregate portfolio     │
│ Calculate drawdown      │
│ Return JSON response    │
└─────────────────────────┘

Data Dependencies

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

ServicePurpose
services/tradebook_service.pyGet today's executed trades
services/positionbook_service.pyGet current positions
services/history_service.pyGet 1-minute historical bars
database/auth_db.pyGet 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

FilePurpose
blueprints/pnltracker.pyBlueprint with P&L calculation logic
services/tradebook_service.pyFetches tradebook from broker
services/positionbook_service.pyFetches current positions
services/history_service.pyFetches historical price data
frontend/src/pages/PnLTracker.tsxReact UI component