TradingView Backtesting

Pareto Frontiers for Trading Strategies: Return, Risk and Turnover

A Pareto frontier contains the strategy candidates that no other feasible candidate improves on at least one chosen objective without worsening another. For trading research, maximize net return while minimizing risk and turnover. The frontier preserves trade-offs; it does not name one unconditional best strategy. With a finite backtest grid, it is the nondominated set among the candidates tested, not proof of a global optimum.

See the decision guide

Define the objectives and the feasible set

For candidate i, write its objectives as (Rᵢ, Dᵢ, Tᵢ): net total return, maximum percentage drawdown and gross traded notional divided by initial capital. Return is maximized; drawdown and turnover are minimized. This is multi-objective optimization because improving one metric can require accepting a worse result in another. Filter out candidates that violate predetermined leverage, liquidity or capital limits before computing the frontier.

  • Candidate A dominates B exactly when R_A ≥ R_B, D_A ≤ D_B and T_A ≤ T_B, with at least one strict inequality. Merely beating B on two of three metrics is insufficient.
  • If a solver minimizes every objective, pass (-R, D, T). Changing dollars to percentages by the same positive scaling does not change dominance; reversing an objective direction does.
  • Hold symbol, dates, currency, initial capital, price adjustments, execution rules and equity sampling frequency constant. A comparison across different test windows answers a different question.
  • Define risk explicitly. Drawdown, volatility and expected shortfall need not produce the same frontier. Here drawdown is a positive loss magnitude, so smaller is preferred.

Five candidates, three distinct frontier points

These hypothetical candidates share three observation intervals and initial equity of 100 account units, with no deposits or withdrawals. Gross P&L is an absolute amount per interval, not a percentage return. Traded amounts count buys and sells separately, once per executed side. The first comparison sets costs to zero only to expose the gross trade-off; it is not an executable profitability claim.

  • B dominates D: 12% ≥ 9%, 3.7736% ≤ 4.7619%, and 6x ≤ 9x. All three comparisons are strict here.
  • A and B are incomparable: A has more return, while B has less drawdown and turnover. B and C are also incomparable. Removing either would discard a trade-off.
  • B and B-copy have equal objective vectors, so neither dominates the other. Show one plotted coordinate with both parameter IDs, or retain both rows. Identical sample metrics do not establish identical trading rules.
  • A return-versus-drawdown plot is only a projection of this three-objective set. Label turnover too; apparent dominance in two dimensions can disappear when the third is included.
CandidateGross P&L by intervalTraded amount by intervalGross returnSampled max drawdownTurnoverGross frontier
A10, -6, 101000, 1000, 100014.00%5.4545%30xYes
B6, -4, 10200, 200, 20012.00%3.7736%6xYes
C4, -2, 5100, 100, 1007.00%1.9231%3xYes
D5, -5, 9300, 300, 3009.00%4.7619%9xNo: B dominates
B-copy6, -4, 10200, 200, 20012.00%3.7736%6xYes: same point as B

Charge costs before recalculating the frontier

Let gᵢ,t be gross P&L, nᵢ,t gross executed notional, c a proportional cost per executed side and E₀ = 100. Compute Eᵢ,t = Eᵢ,t−1 + gᵢ,t − c × nᵢ,t. Then Rᵢ = (Eᵢ,N − E₀) / E₀; Hᵢ,t = max(E₀, …, Eᵢ,t); Dᵢ = max_t((Hᵢ,t − Eᵢ,t) / Hᵢ,t); and Tᵢ = sum_t(nᵢ,t) / E₀. This turnover convention uses initial capital, not average assets, and is not annualized.

  • Ten basis points means c = 0.001. A pays 0.001 × 3000 = 3 units; B pays 0.001 × 600 = 0.6 units. B now has higher net return, lower drawdown and lower turnover than A.
  • For fixed executions, R_net = R_gross − c × T. A and B have equal net return at c = (0.14 − 0.12) / (30 − 6) = 0.00083333, or 8.3333 bps. At that cost B already dominates A because its risk and turnover remain lower.
  • Recompute drawdown along the cost-adjusted path. Subtracting a total fee from final profit while retaining gross drawdown mixes incompatible objectives.
  • This example charges costs at interval boundaries and holds executions fixed. It omits intrainterval equity changes, financing and market impact. If costs alter position sizes, margin calls, orders or fills, rerun the strategy and rebuild every objective.
  • TradingView commission and slippage settings have different units and mechanisms. Slippage is specified in ticks; it is not automatically the proportional cost used here. Do not deduct costs again from exported P&L that already includes them.
CandidateTotal cost at 10 bpsNet equity path including startNet returnNet drawdownNet frontier
A3.0 units100, 109, 102, 11111.00%6.4220%No: B dominates
B0.6 units100, 105.8, 101.6, 111.411.40%3.9698%Yes
C0.3 units100, 103.9, 101.8, 106.76.70%2.0212%Yes
D0.9 units100, 104.7, 99.4, 108.18.10%5.0621%No: B dominates
B-copy0.6 units100, 105.8, 101.6, 111.411.40%3.9698%Yes: same point as B

Run the independent Python calculation

Save this Python 3 code as pareto.py and run python3 pareto.py. It has no external dependencies. The zero-cost IDs are A, B, C and B-copy; at 10 bps they are B, C and B-copy. Comparisons use full floating-point values; rounding happens only in the printed table. The all-pairs calculation takes quadratic time in the candidate count.

  • The code rejects missing intervals, nonfinite values, negative traded amounts and nonpositive initial capital. It excludes paths with nonpositive equity under an explicit feasibility rule.
  • An empty candidate set returns an empty frontier. A single valid candidate survives. Negative returns remain comparable: a less negative return can dominate a larger loss when the other objectives are no worse.
  • Exact duplicates survive because dominance needs a strict improvement. If measurement precision warrants grouping near-equal points, choose and disclose the rounding or tolerance rule first; do not silently round before the comparisons.
Independent educational calculation. This is not a TradingView CSV parser or a built-in Backtest Deep Report feature.python
from math import isfinite

# Hypothetical account units; each interval includes all executed sides.
candidates = {
    "A": ([10, -6, 10], [1000, 1000, 1000]),
    "B": ([6, -4, 10], [200, 200, 200]),
    "C": ([4, -2, 5], [100, 100, 100]),
    "D": ([5, -5, 9], [300, 300, 300]),
    "B-copy": ([6, -4, 10], [200, 200, 200]),
}

def metrics(pnl, traded, cost, initial=100):
    if not pnl or len(pnl) != len(traded):
        raise ValueError("Need matching nonempty interval arrays")
    if not all(isfinite(x) for x in [initial, cost, *pnl, *traded]):
        raise ValueError("Inputs must be finite")
    if initial <= 0 or cost < 0 or any(x < 0 for x in traded):
        raise ValueError("Invalid capital, cost or traded amount")
    equity = peak = initial
    drawdown = 0.0
    for gross, notional in zip(pnl, traded):
        equity += gross - cost * notional
        if equity <= 0:
            raise ValueError("Insolvent path: excluded by this example")
        peak = max(peak, equity)
        drawdown = max(drawdown, (peak - equity) / peak)
    return ((equity - initial) / initial, drawdown, sum(traded) / initial)

def dominates(a, b):
    # Maximize return, minimize drawdown and turnover.
    no_worse = a[0] >= b[0] and a[1] <= b[1] and a[2] <= b[2]
    better = a[0] > b[0] or a[1] < b[1] or a[2] < b[2]
    return no_worse and better

def frontier(points):
    if any(len(p) != 3 or not all(isfinite(x) for x in p)
           or p[1] < 0 or p[2] < 0 for p in points.values()):
        raise ValueError("Need finite return, nonnegative risk and turnover")
    return [name for name, p in points.items()
            if not any(dominates(q, p) for q in points.values())]

for cost in (0, 0.001):
    points = {name: metrics(*inputs, cost) for name, inputs in candidates.items()}
    print(f"Cost {cost * 10000:.0f} bps: {frontier(points)}")
    for name, (ret, dd, turnover) in points.items():
        print(f"  {name}: return={ret:.4%}, drawdown={dd:.4%}, turnover={turnover:g}x")

assert frontier({}) == []
assert frontier({"x": (0, 0, 0)}) == ["x"]
assert not dominates((0.12, 0.04, 6), (0.12, 0.04, 6))
assert dominates((0.12, 0.03, 6), (0.12, 0.04, 6))
assert dominates((-0.01, 0.03, 6), (-0.02, 0.04, 7))

Build comparable inputs from TradingView results

Export the List of Trades and the required summary metrics for each frozen parameter set. TradingView exports report tabs separately. Preserve the run identifier, dates, strategy properties and cost assumptions with each file. A list of closed trades alone does not establish a regularly sampled mark-to-market account equity series.

  • The Backtest Deep Report importer reads trade IDs, entry/exit types, timestamps, prices, quantities and net P&L. Its local overview sums trade net P&L and calculates drawdown from cumulative closed-trade equity. That path can miss losses while positions are open.
  • For a mark-to-market frontier, obtain comparable account equity observations including open positions for every candidate. Use the same observation grid and reconcile its final value with the report.
  • Build executed notional from reconciled fills, including entries, exits and partial executions. Apply contract multipliers and currency conversion where required; price times quantity is not universally sufficient. Trade count is not turnover.
  • The current report does not supply this multi-candidate Pareto calculation or the initial-capital turnover definition used here. Keep a separate candidate table and run the independent calculation after validating its inputs.

Choose a candidate only after stating a preference

At 10 bps, B offers 11.4% sampled return with 3.9698% drawdown and 6x turnover, while C offers 6.7%, 2.0212% and 3x. Neither dominates the other. A predeclared turnover limit of 4x excludes B and leaves C; without that limit, the frontier supplies no reason to discard B. The limit is a research assumption, not a recommended trading rule.

  • A weighted score introduces preferences and depends on units. Normalize and disclose its weights if used; the score is a separate selection rule, not the definition of Pareto dominance.
  • A sampled frontier is sensitive to noise, candidate coverage and repeated parameter searches. Keep the tested candidate count and compare fixed finalists on data excluded from selection.
  • Rebuild the frontier under plausible costs and different validation periods. A candidate may survive one scenario and be dominated in another. A short historical sample cannot establish future dominance.
  • Store objective values with parameter IDs and assumptions. A frontier can contain many points or even every candidate when objectives conflict; adding more objectives can make filtering less selective.
Where Pineify fits

Backtest Deep Report

Import TradingView trade CSV files to inspect supported profit and closed-trade risk results. Maintain the candidate table and Pareto calculation separately.

Also useful: Pine Script AI Coding Agent. Specify strategy rules, cost assumptions and required outputs, then review and validate generated Pine Script before comparing its backtests.

Explore Backtest Deep Report

Frequently asked questions

Educational information, not investment advice. All candidate results are hypothetical. Historical nondominance does not guarantee future returns, lower losses or achievable fills.

Sources and verification

Published by Pineify. Pineify is an independent product and is not affiliated with TradingView. Sources support the technical explanations; examples do not establish trading performance.