Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

Building a Modular PNL Tracker for TradingView PineScript Strategies

10 min read

Professional traders need comprehensive performance metrics to evaluate their strategies effectively. This tutorial demonstrates how to build a reusable PNL (Profit and Loss) tracking module that integrates seamlessly with any Pine Script strategy. The module provides real-time performance analytics directly on your TradingView charts.

Overview

The PnL tracker module consists of three main components:

  • Tracking Variables: Store trade statistics and performance metrics
  • Calculation Logic: Process trades and update metrics automatically
  • Dashboard Display: Visual representation of performance data

Complete Code Implementation

// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Modular PnL Tracking System - Can be attached to any strategy

//@version=6
strategy("Strategy with Modular PnL Tracker [Example: Supertrend]", overlay=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=100)

// ========================================
// MODULE: PNL TRACKER INPUTS
// ========================================
showPnLDashboard = input.bool(true, "Show PnL Dashboard", group="PnL Tracker Settings")
dashboardLocation = input.string("Top Right", "Dashboard Location", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group="PnL Tracker Settings")
dashboardTextSize = input.string("Normal", "Text Size", options=["Small", "Normal", "Large"], group="PnL Tracker Settings")
bgTransparency = input.int(90, "Background Transparency", minval=0, maxval=100, group="PnL Tracker Settings")
showExtendedMetrics = input.bool(true, "Show Extended Metrics", group="PnL Tracker Settings")
showStreakInfo = input.bool(true, "Show Streak Information", group="PnL Tracker Settings")

// Color Settings
profitTextColor = input.color(color.lime, "Profit Text Color", group="PnL Tracker Colors")
lossTextColor = input.color(color.red, "Loss Text Color", group="PnL Tracker Colors")
neutralTextColor = input.color(color.gray, "Neutral Text Color", group="PnL Tracker Colors")
headerBgColor = input.color(color.navy, "Header Background", group="PnL Tracker Colors")

// ========================================
// MODULE: PNL TRACKING VARIABLES
// ========================================
// Core PnL Variables
var float pnl_entryPrice = na
var int pnl_totalTrades = 0
var int pnl_winningTrades = 0
var int pnl_losingTrades = 0
var float pnl_totalPnL = 0.0
var float pnl_totalWinPnL = 0.0
var float pnl_totalLossPnL = 0.0
var float pnl_maxWin = 0.0
var float pnl_maxLoss = 0.0
var float pnl_lastPnL = 0.0

// Equity and Drawdown
var float pnl_equity = strategy.initial_capital
var float pnl_peakEquity = strategy.initial_capital
var float pnl_maxDrawdown = 0.0
var float pnl_currentDrawdown = 0.0

// Streak Tracking
var int pnl_currentStreak = 0
var int pnl_maxWinStreak = 0
var int pnl_maxLossStreak = 0
var bool pnl_inWinStreak = true

// Extended Metrics
var float pnl_avgWin = 0.0
var float pnl_avgLoss = 0.0
var float pnl_profitFactor = 0.0
var float pnl_winRate = 0.0
var float pnl_expectancy = 0.0
var float pnl_payoffRatio = 0.0

// Performance Arrays
var float[] pnl_tradeReturns = array.new_float(0)
var int pnl_lastClosedTrades = 0

// ========================================
// MODULE: PNL TRACKING LOGIC
// ========================================
// Update PnL metrics when a trade closes
if (strategy.closedtrades > pnl_lastClosedTrades)
    // Get last trade info
    lastProfit = strategy.closedtrades.profit(strategy.closedtrades - 1)
    
    // Update trade counter
    pnl_totalTrades := strategy.closedtrades
    pnl_lastClosedTrades := strategy.closedtrades
    
    // Update PnL
    pnl_totalPnL := pnl_totalPnL + lastProfit
    pnl_lastPnL := lastProfit
    
    // Track trade returns for statistics
    array.push(pnl_tradeReturns, lastProfit)
    if (array.size(pnl_tradeReturns) > 100)
        array.shift(pnl_tradeReturns)
    
    // Update win/loss stats
    if (lastProfit > 0)
        pnl_winningTrades := pnl_winningTrades + 1
        pnl_totalWinPnL := pnl_totalWinPnL + lastProfit
        pnl_maxWin := math.max(pnl_maxWin, lastProfit)
        
        // Update win streak
        if (pnl_inWinStreak)
            pnl_currentStreak := pnl_currentStreak + 1
        else
            pnl_inWinStreak := true
            pnl_currentStreak := 1
        pnl_maxWinStreak := math.max(pnl_maxWinStreak, pnl_currentStreak)
    else
        pnl_losingTrades := pnl_losingTrades + 1
        pnl_totalLossPnL := pnl_totalLossPnL + lastProfit
        pnl_maxLoss := math.min(pnl_maxLoss, lastProfit)
        
        // Update loss streak
        if (not pnl_inWinStreak)
            pnl_currentStreak := pnl_currentStreak + 1
        else
            pnl_inWinStreak := false
            pnl_currentStreak := 1
        pnl_maxLossStreak := math.max(pnl_maxLossStreak, pnl_currentStreak)

// Calculate extended metrics
if (pnl_totalTrades > 0)
    pnl_winRate := (pnl_winningTrades / pnl_totalTrades) * 100
    pnl_avgWin := pnl_winningTrades > 0 ? pnl_totalWinPnL / pnl_winningTrades : 0
    pnl_avgLoss := pnl_losingTrades > 0 ? math.abs(pnl_totalLossPnL / pnl_losingTrades) : 0
    pnl_profitFactor := pnl_totalLossPnL != 0 ? math.abs(pnl_totalWinPnL / pnl_totalLossPnL) : pnl_totalWinPnL > 0 ? 999 : 0
    pnl_expectancy := (pnl_winRate / 100 * pnl_avgWin) - ((100 - pnl_winRate) / 100 * pnl_avgLoss)
    pnl_payoffRatio := pnl_avgLoss != 0 ? pnl_avgWin / pnl_avgLoss : 0

// Update equity and drawdown
pnl_equity := strategy.initial_capital + strategy.netprofit
pnl_peakEquity := math.max(pnl_peakEquity, pnl_equity)
pnl_currentDrawdown := ((pnl_peakEquity - pnl_equity) / pnl_peakEquity) * 100
pnl_maxDrawdown := math.max(pnl_maxDrawdown, pnl_currentDrawdown)

// Helper function for color (moved outside of displayPnLDashboard)
getMetricColor(val) =>
    val > 0 ? profitTextColor : val < 0 ? lossTextColor : neutralTextColor

// ========================================
// MODULE: DASHBOARD DISPLAY FUNCTION
// ========================================
displayPnLDashboard() =>
    if (showPnLDashboard)
        // Determine position
        tablePos = dashboardLocation == "Top Right" ? position.top_right : dashboardLocation == "Top Left" ? position.top_left : dashboardLocation == "Bottom Right" ? position.bottom_right : position.bottom_left
        
        // Determine text size
        textSz = dashboardTextSize == "Small" ? size.small : dashboardTextSize == "Large" ? size.large : size.normal
        
        // Create table with sufficient rows (30 to be safe)
        var table pnlTable = table.new(tablePos, 2, 30, bgcolor=color.new(color.black, bgTransparency), frame_color=color.gray, frame_width=1, border_color=color.gray, border_width=1)
        
        row = 0
        
        // HEADER
        table.cell(pnlTable, 0, row, "PNL TRACKER", text_color=color.white, text_size=textSz, bgcolor=headerBgColor, text_halign=text.align_center)
        table.merge_cells(pnlTable, 0, row, 1, row)
        row += 1
        
        // SEPARATOR
        table.cell(pnlTable, 0, row, "────────────────", text_color=color.gray, text_size=textSz)
        table.merge_cells(pnlTable, 0, row, 1, row)
        row += 1
        
        // ACCOUNT METRICS
        table.cell(pnlTable, 0, row, "Equity:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring(pnl_equity, "#,##0.00"), text_color=getMetricColor(pnl_equity - strategy.initial_capital), text_size=textSz, text_halign=text.align_right)
        row += 1
        
        table.cell(pnlTable, 0, row, "Total PnL:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring(pnl_totalPnL, "#,##0.00"), text_color=getMetricColor(pnl_totalPnL), text_size=textSz, text_halign=text.align_right)
        row += 1
        
        table.cell(pnlTable, 0, row, "PnL %:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring((pnl_totalPnL/strategy.initial_capital)*100, "#0.00") + "%", text_color=getMetricColor(pnl_totalPnL), text_size=textSz, text_halign=text.align_right)
        row += 1
        
        table.cell(pnlTable, 0, row, "Last Trade:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring(pnl_lastPnL, "#,##0.00"), text_color=getMetricColor(pnl_lastPnL), text_size=textSz, text_halign=text.align_right)
        row += 1
        
        // SEPARATOR
        table.cell(pnlTable, 0, row, "────────────────", text_color=color.gray, text_size=textSz)
        table.merge_cells(pnlTable, 0, row, 1, row)
        row += 1
        
        // TRADE STATISTICS
        table.cell(pnlTable, 0, row, "Total Trades:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring(pnl_totalTrades), text_color=neutralTextColor, text_size=textSz, text_halign=text.align_right)
        row += 1
        
        table.cell(pnlTable, 0, row, "Winning:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring(pnl_winningTrades), text_color=profitTextColor, text_size=textSz, text_halign=text.align_right)
        row += 1
        
        table.cell(pnlTable, 0, row, "Losing:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring(pnl_losingTrades), text_color=lossTextColor, text_size=textSz, text_halign=text.align_right)
        row += 1
        
        table.cell(pnlTable, 0, row, "Win Rate:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
        table.cell(pnlTable, 1, row, str.tostring(pnl_winRate, "#0.00") + "%", text_color=pnl_winRate >= 50 ? profitTextColor : lossTextColor, text_size=textSz, text_halign=text.align_right)
        row += 1
        
        // EXTENDED METRICS
        if (showExtendedMetrics and row < 29)
            table.cell(pnlTable, 0, row, "────────────────", text_color=color.gray, text_size=textSz)
            table.merge_cells(pnlTable, 0, row, 1, row)
            row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Avg Win:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(pnl_avgWin, "#0.00"), text_color=profitTextColor, text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Avg Loss:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(pnl_avgLoss, "#0.00"), text_color=lossTextColor, text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Max Win:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(pnl_maxWin, "#0.00"), text_color=profitTextColor, text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Max Loss:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(math.abs(pnl_maxLoss), "#0.00"), text_color=lossTextColor, text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Profit Factor:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                pfStr = pnl_profitFactor > 100 ? ">100" : str.tostring(pnl_profitFactor, "#0.00")
                table.cell(pnlTable, 1, row, pfStr, text_color=pnl_profitFactor >= 1 ? profitTextColor : lossTextColor, text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Expectancy:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(pnl_expectancy, "#0.00"), text_color=getMetricColor(pnl_expectancy), text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Payoff Ratio:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(pnl_payoffRatio, "#0.00"), text_color=pnl_payoffRatio >= 1 ? profitTextColor : lossTextColor, text_size=textSz, text_halign=text.align_right)
                row += 1
        
        // RISK METRICS
        if row < 29
            table.cell(pnlTable, 0, row, "────────────────", text_color=color.gray, text_size=textSz)
            table.merge_cells(pnlTable, 0, row, 1, row)
            row += 1
        
        if row < 29
            table.cell(pnlTable, 0, row, "Current DD:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
            table.cell(pnlTable, 1, row, str.tostring(pnl_currentDrawdown, "#0.00") + "%", text_color=pnl_currentDrawdown > 5 ? lossTextColor : neutralTextColor, text_size=textSz, text_halign=text.align_right)
            row += 1
        
        if row < 29
            table.cell(pnlTable, 0, row, "Max DD:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
            table.cell(pnlTable, 1, row, str.tostring(pnl_maxDrawdown, "#0.00") + "%", text_color=lossTextColor, text_size=textSz, text_halign=text.align_right)
            row += 1
        
        // STREAK INFO
        if (showStreakInfo and row < 29)
            table.cell(pnlTable, 0, row, "────────────────", text_color=color.gray, text_size=textSz)
            table.merge_cells(pnlTable, 0, row, 1, row)
            row += 1
            
            if row < 29
                streakStr = pnl_inWinStreak ? "W" + str.tostring(pnl_currentStreak) : "L" + str.tostring(pnl_currentStreak)
                streakColor = pnl_inWinStreak ? profitTextColor : lossTextColor
                
                table.cell(pnlTable, 0, row, "Current Streak:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, streakStr, text_color=streakColor, text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Max Win Streak:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(pnl_maxWinStreak), text_color=profitTextColor, text_size=textSz, text_halign=text.align_right)
                row += 1
            
            if row < 29
                table.cell(pnlTable, 0, row, "Max Loss Streak:", text_color=color.white, text_size=textSz, text_halign=text.align_left)
                table.cell(pnlTable, 1, row, str.tostring(pnl_maxLossStreak), text_color=lossTextColor, text_size=textSz, text_halign=text.align_right)

// ========================================
// EXAMPLE STRATEGY: SUPERTREND
// ========================================
// This section contains your actual strategy logic
// Replace this with ANY strategy you want

// Strategy Parameters
factor = input.float(3.0, "Supertrend Multiplier", minval=1.0, maxval=10.0, step=0.1, group="Strategy Settings")
length = input.int(10, "ATR Length", minval=1, maxval=100, step=1, group="Strategy Settings")

// Calculate Supertrend
[supertrend, direction] = ta.supertrend(factor, length)

// Plot Supertrend
plot(supertrend, "Supertrend", color=direction == -1 ? color.green : color.red, linewidth=2)

// Trading Signals
buy = direction == -1 and direction[1] == 1
sell = direction == 1 and direction[1] == -1

// Track entry price for the module
if (buy or sell)
    pnl_entryPrice := close

// Execute Trades
if (buy)
    strategy.entry("BUY", direction=strategy.long)

if (sell)
    strategy.entry("SHORT", direction=strategy.short)

// ========================================
// MODULE: CALL PNL TRACKER
// ========================================
// The PnL tracking logic now runs inline (not in functions)
// This avoids the "cannot modify global variable" error

// Display the dashboard
displayPnLDashboard()

// ========================================
// OPTIONAL: VISUAL ENHANCEMENTS
// ========================================
// Background coloring based on position (subtle)
bgcolor(strategy.position_size > 0 ? color.new(color.green, 95) : strategy.position_size < 0 ? color.new(color.red, 95) : na)

Strategy Settings

//@version=6
strategy("Strategy with Modular PnL Tracker [Example: Supertrend]", overlay=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=100)

// ========================================
// MODULE: PNL TRACKER INPUTS
// ========================================
showPnLDashboard = input.bool(true, "Show PnL Dashboard", group="PnL Tracker Settings")
dashboardLocation = input.string("Top Right", "Dashboard Location", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group="PnL Tracker Settings")
dashboardTextSize = input.string("Normal", "Text Size", options=["Small", "Normal", "Large"], group="PnL Tracker Settings")
bgTransparency = input.int(90, "Background Transparency", minval=0, maxval=100, group="PnL Tracker Settings")
showExtendedMetrics = input.bool(true, "Show Extended Metrics", group="PnL Tracker Settings")
showStreakInfo = input.bool(true, "Show Streak Information", group="PnL Tracker Settings")

// Color Settings
profitTextColor = input.color(color.lime, "Profit Text Color", group="PnL Tracker Colors")
lossTextColor = input.color(color.red, "Loss Text Color", group="PnL Tracker Colors")
neutralTextColor = input.color(color.gray, "Neutral Text Color", group="PnL Tracker Colors")
headerBgColor = input.color(color.navy, "Header Background", group="PnL Tracker Colors")

Core Tracking Variables

The module uses prefixed variables (pnl_) to avoid naming conflicts with existing strategy code:

// Core PnL Variables
var float pnl_entryPrice = na
var int pnl_totalTrades = 0
var int pnl_winningTrades = 0
var int pnl_losingTrades = 0
var float pnl_totalPnL = 0.0
var float pnl_totalWinPnL = 0.0
var float pnl_totalLossPnL = 0.0
var float pnl_maxWin = 0.0
var float pnl_maxLoss = 0.0
var float pnl_lastPnL = 0.0

// Equity and Drawdown
var float pnl_equity = strategy.initial_capital
var float pnl_peakEquity = strategy.initial_capital
var float pnl_maxDrawdown = 0.0
var float pnl_currentDrawdown = 0.0

// Streak Tracking
var int pnl_currentStreak = 0
var int pnl_maxWinStreak = 0
var int pnl_maxLossStreak = 0
var bool pnl_inWinStreak = true

Trade Processing Logic

The module automatically detects closed trades and updates all metrics:

// Update PnL metrics when a trade closes
if (strategy.closedtrades > pnl_lastClosedTrades)
    // Get last trade info
    lastProfit = strategy.closedtrades.profit(strategy.closedtrades - 1)
    
    // Update trade counter
    pnl_totalTrades := strategy.closedtrades
    pnl_lastClosedTrades := strategy.closedtrades
    
    // Update PnL
    pnl_totalPnL := pnl_totalPnL + lastProfit
    pnl_lastPnL := lastProfit
    
    // Update win/loss stats
    if (lastProfit > 0)
        pnl_winningTrades := pnl_winningTrades + 1
        pnl_totalWinPnL := pnl_totalWinPnL + lastProfit
        pnl_maxWin := math.max(pnl_maxWin, lastProfit)
    else
        pnl_losingTrades := pnl_losingTrades + 1
        pnl_totalLossPnL := pnl_totalLossPnL + lastProfit
        pnl_maxLoss := math.min(pnl_maxLoss, lastProfit)

Performance Metrics Calculation

Advanced metrics provide deeper insights into strategy performance:

// Calculate extended metrics
if (pnl_totalTrades > 0)
    pnl_winRate := (pnl_winningTrades / pnl_totalTrades) * 100
    pnl_avgWin := pnl_winningTrades > 0 ? pnl_totalWinPnL / pnl_winningTrades : 0
    pnl_avgLoss := pnl_losingTrades > 0 ? math.abs(pnl_totalLossPnL / pnl_losingTrades) : 0
    pnl_profitFactor := pnl_totalLossPnL != 0 ? math.abs(pnl_totalWinPnL / pnl_totalLossPnL) : pnl_totalWinPnL > 0 ? 999 : 0
    pnl_expectancy := (pnl_winRate / 100 * pnl_avgWin) - ((100 - pnl_winRate) / 100 * pnl_avgLoss)
    pnl_payoffRatio := pnl_avgLoss != 0 ? pnl_avgWin / pnl_avgLoss : 0

Dashboard Display Function

The visual dashboard presents all metrics in an organized table format:

displayPnLDashboard() =>
    if (showPnLDashboard)
        // Determine position
        tablePos = dashboardLocation == "Top Right" ? position.top_right : dashboardLocation == "Top Left" ? position.top_left : dashboardLocation == "Bottom Right" ? position.bottom_right : position.bottom_left
        
        // Create table
        var table pnlTable = table.new(tablePos, 2, 30, bgcolor=color.new(color.black, bgTransparency), frame_color=color.gray, frame_width=1, border_color=color.gray, border_width=1)
        
        // Populate table with metrics
        row = 0
        table.cell(pnlTable, 0, row, "PNL TRACKER", text_color=color.white, bgcolor=headerBgColor)
        // ... additional rows for each metric

Integration Steps

Step 1: Add the Module

Copy the complete PnL tracker code (inputs, variables, and logic) to the beginning of your strategy script.

Step 2: Track Entry Prices

Add entry price tracking when your strategy generates signals:

// Your existing buy/sell conditions
if (buy or sell)
    pnl_entryPrice := close

Step 3: Activate the Dashboard

Add this single line at the end of your script:

displayPnLDashboard()

Example Integration: Supertrend Strategy

Here’s how the module integrates with a simple Supertrend strategy:

// Strategy Parameters
factor = input.float(3.0, "Supertrend Multiplier", minval=1.0, maxval=10.0, step=0.1)
length = input.int(10, "ATR Length", minval=1, maxval=100, step=1)

// Calculate Supertrend
[supertrend, direction] = ta.supertrend(factor, length)

// Trading Signals
buy = direction == -1 and direction[1] == 1
sell = direction == 1 and direction[1] == -1

// Track entry price for PnL module
if (buy or sell)
    pnl_entryPrice := close

// Execute Trades
if (buy)
    strategy.entry("BUY", direction=strategy.long)
if (sell)
    strategy.entry("SHORT", direction=strategy.short)

// Display PnL Dashboard
displayPnLDashboard()

Metrics Explained

Basic Metrics

  • Equity: Current account balance including unrealized P&L
  • Total PnL: Cumulative profit/loss from all closed trades
  • Win Rate: Percentage of profitable trades
  • Total Trades: Number of completed trades

Advanced Metrics

  • Profit Factor: Ratio of gross profit to gross loss (values > 1.0 indicate profitability)
  • Expectancy: Average expected profit per trade
  • Payoff Ratio: Average win divided by average loss
  • Maximum Drawdown: Largest peak-to-trough decline in equity

Streak Analysis

  • Current Streak: Consecutive wins or losses
  • Max Win/Loss Streak: Historical maximum consecutive wins/losses

Customization Options

Visual Settings

  • Dashboard position (four corner options)
  • Text size (Small/Normal/Large)
  • Background transparency (0-100%)
  • Custom colors for profit/loss/neutral states

Display Options

  • Toggle extended metrics on/off
  • Show/hide streak information
  • Enable/disable entire dashboard

Performance Considerations

  1. Variable Naming: All module variables use pnl_ prefix to prevent conflicts
  2. Table Management: Fixed 30-row table prevents runtime errors
  3. Calculation Efficiency: Metrics update only when trades close
  4. Memory Usage: Stores last 100 trades for statistical analysis

Common Implementation Patterns

Pattern 1: Minimal Integration

For basic PnL tracking without modifications:

// Add module code at script start
// Add entry price tracking in strategy logic
// Call displayPnLDashboard() at script end

Pattern 2: Selective Metrics

Display only essential metrics:

showExtendedMetrics = false
showStreakInfo = false

Pattern 3: Custom Positioning

Adapt dashboard location based on chart layout:

dashboardLocation = "Bottom Right"  // Avoids overlap with price action

Troubleshooting Guide

Issue: Dashboard Not Appearing

  • Verify showPnLDashboard = true
  • Check dashboard position isn’t off-screen
  • Ensure displayPnLDashboard() is called

Issue: Metrics Not Updating

  • Confirm trades are actually closing
  • Verify entry price tracking is implemented
  • Check strategy has sufficient historical data

Issue: Visual Conflicts

  • Adjust transparency settings
  • Change dashboard position
  • Modify text size for better readability

Best Practices

  1. Test First: Run the module on historical data before live trading
  2. Monitor Performance: Review metrics regularly to identify strategy issues
  3. Adjust Position Sizing: Use metrics to optimize position sizes
  4. Document Changes: Keep notes when modifying metric calculations
  5. Version Control: Save different versions when experimenting with modifications

Conclusion

This modular PnL tracker transforms any Pine Script strategy into a professional trading system with comprehensive performance analytics. The plug-and-play design ensures compatibility with existing strategies while providing flexibility through extensive customization options. Implement this module to gain deeper insights into strategy performance and make data-driven trading decisions.

Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

3 Replies to “Building a Modular PNL Tracker for TradingView PineScript Strategies”

  1. Thank you for the script. I have integrated into my strategy and it works well. It would be good to have details in loss or profit on both sides like short or long. Currently it shows max loss or max win or avg loss or avg win but with shorts or longs is missing. Same with winning or losing trades. If this can be incorporated it would be good or more beneficial.

Leave a Reply

Get Notifications, Alerts on Market Updates, Trading Tools, Automation & More