Why Free TradingView Isn't Enough — Designing a Python Backtester That Feels Like TradingView

📘 Educational — not financial advice. This post describes the architecture of an open-source backtesting harness for personal use. It is not investment advice, a trade signal, or a claim that any specific strategy will be profitable. Backtested results — including any produced later in this series — describe past behaviour on historical data and do not guarantee future performance. See full Disclaimer.

"A backtest report you cannot reproduce with code you can read is a very expensive way to feel confident about a trading idea. The point of building a small Python harness is not that Python is faster than Pine — it is that when the report looks wrong, you can go read the eighty lines that produced it."

In post 1 I walked through Pine Script v5 as a language and mentioned in passing that the free TradingView tier eventually stops being enough for real backtesting. This post is the long form of that observation. It maps the four specific walls the free tier puts in your way, spells out the TradingView Strategy Tester semantics that need to be preserved, and lays out the four-layer Python harness that the rest of the series will build.

The harness is not a trading framework. It is a small, deliberately simple piece of Python — pandas plus matplotlib, plus yfinance and ccxt for data — that reproduces the essentials of a TradingView strategy report on OHLCV data you own on disk. That last part matters, because half the reason to leave the free tier is not "TradingView is bad" — it is that you want the same rule set to run on the same data, on your own machine, without a platform between you and the report.

📋 What we will look at

  1. The four walls of free TradingView Strategy Tester
  2. TV Strategy Tester semantics — what we have to reproduce
  3. The four-layer architecture (data → engine → metrics → viz)
  4. Directory layout — the folder tree you will grow
  5. Why build from scratch instead of using Backtrader or vectorbt

1. The Four Walls of Free TradingView Strategy Tester

Every free-tier user hits the same four walls, in roughly this order:

  1. Bar-count ceiling on strategies. The free tier caps the number of historical bars a strategy can iterate over on any given chart. On a daily chart this is enough for many use cases; on a 15-minute chart it is not enough for three years of coverage, let alone the multi-year, multi-regime window most edges actually need to be seen in.
  2. Single-symbol, single-timeframe scope per script instance. A strategy is bound to the chart. You can pull higher-timeframe data via request.security(), but you cannot easily loop the same strategy across a universe (twenty tickers, three timeframes each) and get one consolidated report. Every combination is a manual chart swap.
  3. Metrics ceiling in the Strategy Tester panel. The free tier shows Net Profit, Profit Factor, Max Drawdown, Win Rate, Total Trades, and a trade list. It does not show Sharpe, Sortino, expectancy, MFE/MAE distributions, or per-regime slices. It also does not offer walk-forward or parameter-sweep tooling.
  4. No systematic optimisation. If you want to know how a parameter changes performance, you have to change the input, re-run, screenshot, change the input, re-run, screenshot. There is no programmatic loop. This is where the "why does my Pine backtest look nothing like the real world" question is often really "I optimised on a chart until it stopped complaining about drawdown."

The first two walls are platform pricing decisions. The third is a display constraint. The fourth is the one that actually damages trading decisions, because it selects for whichever parameter set the eye landed on when the equity curve looked good. All four have the same cure: run the same rule set on the same data outside TradingView, in code you can read.

2. TV Strategy Tester Semantics — What We Have to Reproduce

The point is not to replace TradingView — Pine Script remains the fastest way to see an idea on a chart. The point is to reproduce the parts of the Strategy Tester report that anchor a decision, so that when the Python and Pine numbers disagree, you know which assumption is being violated.

These are the behaviours the harness needs to match, in order of how much they can distort a report if you get them wrong:

Bar-by-bar iteration. No vectorised shortcut may use the current or future bar to make a decision on the current bar. Every signal is evaluated using only what is known when that bar closes.

Next-bar fills. An order placed on bar N fills at the open of bar N+1. This matches TradingView's default fill assumption and is the single biggest reason casual Python backtests inflate returns compared to Pine — they fill at close of bar N and pocket the free money.

Commission on entry and exit. Both legs pay. For futures we use $2 round-trip per contract as a realistic MNQ number; for crypto we default to 0.1% per side.

Slippage as an adverse tick. The default assumption is one tick against you on every fill. This is generous — real slippage is worse on illiquid instruments — but it is at least honest about the direction the error runs in.

Sizing by percent of equity or fixed contracts. The percent-of-equity path lets the equity curve compound naturally; the fixed-contracts path connects to the position sizing framework from an earlier post so R-multiple reasoning stays consistent across the blog.

Everything else in TradingView's report — Sharpe, Sortino, per-regime performance, MFE/MAE — is downstream of these five choices. If the fills are right and the fees are right, the aggregate metrics are computable. If the fills are wrong, no amount of clever statistics will save the report.

3. The Four-Layer Architecture

A clean four-layer architecture diagram on a light off-white background reading left to right, with an ingest column labeled Data showing yfinance and ccxt sources arriving as OHLCV DataFrames, an orchestration column labeled Engine showing four boxes for broker with commission and slippage rules, portfolio tracking positions and equity, strategy base class with an on-bar hook, and runner containing the bar-by-bar event loop, an analysis column labeled Metrics showing a stack of computed statistics including Net Profit, Profit Factor, Sharpe, Sortino, Max Drawdown, and Win Rate, and finally an output column labeled Viz showing a matplotlib equity curve chart with a drawdown subplot and a small stack labeled HTML Report Export, connected left to right by arrows that make the dataflow obvious and titled at the top with the wording Coder Trader Backtester Architecture v0.1 to reinforce that the diagram is the specification for what the next three posts of the series will build in code and not a marketing overview of what it might look like when finished.

🔼 Figure 1: the four layers of the backtester, left to right. Each column is one blog post in the sequence — posts 3, 4, and 5 build layers 1, 2, and 3 respectively, and layer 4 lands with layer 3 because a metric without an equity curve is not something anyone reads.

The harness is built as four cooperating layers. Each layer has a single job and is small enough that a reader can hold the whole thing in their head:

  • Data layer — thin wrappers around yfinance for equities and futures proxies, and ccxt for crypto exchanges. OHLCV is normalised into a pandas DataFrame with UTC timestamps, cached to Parquet, and resampled to the requested timeframe. This layer knows nothing about strategies.
  • Engine layer — the bar-by-bar event loop, an order book with fill logic that matches TradingView's next-bar-open semantics, a portfolio that tracks position, equity, and cash, and a Strategy base class with an on_bar() hook. This is the layer where "your idea" plugs in.
  • Metrics layer — takes the trade list and equity series from a completed run and produces the numbers a report needs: Net Profit, Profit Factor, Sharpe, Sortino, Max Drawdown ($ and %), Win Rate, Average Trade, Total Bars in Trade. Every metric is computed from the trade list — the equity series is a redundant check.
  • Visualisation layer — matplotlib for the equity curve, drawdown subplot, and per-trade markers on the price chart; a small HTML export for a shareable four-tab report modelled on TradingView's Overview / Trades / Performance / List-of-Trades layout.

Each layer is one module or a small package. The whole thing is deliberately smaller than a general-purpose backtesting library — because it is not a general-purpose backtesting library. It is a harness for the specific kinds of ideas this blog runs on.

4. Directory Layout — the Folder Tree You Will Grow

The directory tree below is the same one that lives on my machine right now. Every folder is scaffolded and every folder will get its content filled in across the next posts. The names are unglamorous on purpose — an engineer opening this cold should be able to guess where things live.

backtester/
├── README.md              overview + roadmap
├── requirements.txt       pandas, numpy, yfinance, ccxt, matplotlib, pyarrow, pytest
├── .gitignore
├── data/                  ← post 3
│   ├── yahoo.py           yfinance wrapper + Parquet cache
│   ├── crypto.py          ccxt wrapper (Binance, Bybit)
│   └── resample.py        1m → 5m → 1H → 4H → 1D
├── engine/                ← post 4
│   ├── broker.py          next-open fills, commission, slippage
│   ├── portfolio.py       positions, equity, cash
│   ├── strategy.py        Strategy base class + on_bar() hook
│   └── runner.py          bar-by-bar event loop
├── indicators/            ← posts 4–8, one at a time
│   ├── ma.py
│   ├── rsi.py
│   ├── supertrend.py      ← post 8
│   ├── obv.py
│   ├── cmf.py
│   └── divergence.py      ← post 6
├── metrics/               ← post 5
│   └── stats.py           net_profit, profit_factor, sharpe, sortino,
│                          max_drawdown, win_rate, expectancy
├── viz/                   ← post 5
│   └── plot.py            equity + drawdown + candles + HTML report
├── examples/              ← posts 4, 6, 7, 8, 9, 10
│   ├── 01_sma_cross.py    ← post 4 (smoke test)
│   ├── 02_obv_divergence.py
│   ├── 03_cmf_200ema.py
│   ├── 04_supertrend_rsi.py
│   ├── 05_five_layer_scanner.py
│   └── 06_grade_classifier.py
├── pine/                  ← posts 9–10 (companion Pine sources)
│   ├── 05_five_layer_scanner.pine
│   └── 06_grade_classifier.pine
└── tests/                 ← posts 3, 4, 5
    ├── test_data_load.py
    ├── test_broker.py
    └── test_metrics.py


VS Code Explorer sidebar screenshot in dark theme showing the backtester folder fully expanded with its subfolders data engine indicators metrics viz examples pine and tests all visible, each subfolder showing at least a placeholder __init__.py or README.md file, plus the README.md file open in the main editor pane on the right showing the Design Goals section with numbered items about bar-by-bar iteration next-open fills and commission and slippage modelling, framed at a resolution that lets a reader clearly follow the scaffolding relationship between the tree and the roadmap and to confirm that the backtester package really does exist as an actual directory rather than as a rendered wireframe.

🔼 Figure 2: the same tree, taken from the local repository. Every subfolder currently contains only a placeholder — the point is that the shape is committed before the code, so that each subsequent post fills a known slot instead of inventing one.

The scaffold is the specification. When post 3 lands, all it does is fill data/*.py. When post 4 lands, it fills engine/*.py and adds examples/01_sma_cross.py plus tests/test_broker.py. Every module has a home before it has a body — which turns out to be the single biggest habit of engineering that transfers well to a trading project, as I described more generally in Reading Charts After a Decade of Reading Code.

5. Why Build From Scratch Instead of Using Backtrader or vectorbt

Two questions I get every time I describe this project: "why not vectorbt?" and "why not backtrader?" Both are reasonable — both are also the reason building this from scratch is the right call for a blog series.

⚠️ FOUR WAYS A BACKTEST QUIETLY LIES TO YOU

① Look-ahead bias

Using bar N's close (or, worse, bar N+1's high) inside a signal that is supposed to fire at bar N's close. Vectorised backtesters are especially prone to this because a shifted series is one keystroke away from an unshifted one, and both compile. In an audit of common community strategies, 40–45% of "profitable" Pine-to-Python translations have quietly reintroduced look-ahead somewhere in the port.

② Survivorship bias

Backtesting today's S&P 500 members over the last twenty years and treating the result as the S&P 500's twenty-year performance. The delisted names are the ones you actually lost money on, and Yahoo Finance is quiet about them. The harness cannot fix this at the data layer for you — but it can, and does, refuse to pretend the problem is not there.

③ Overfitting via parameter mining

Running a parameter sweep and picking the combination with the best in-sample Sharpe is not a research method, it is a statistical accident generator. The harness deliberately keeps sweeps ugly to run — you have to write the loop yourself — because that friction is the single most effective anti-overfitting mechanism in an amateur trader's toolkit.

④ Ignoring slippage and liquidity

Assuming your fills land at the mid, or at yesterday's close, or at the modelled bar-open with zero adverse move. The harness's default is one adverse tick per fill plus commission at both ends; that is a floor, not a target. Anyone trading anything less liquid than QQQ should raise it.

Both vectorbt and backtrader are honest about these problems, but neither exposes fewer than two thousand lines of surface area to explain them. For a blog series where the point is to read what makes the report land where it lands, a bespoke ~250-line harness is the pedagogical better choice. The fifth and last reason is smaller and more subjective: I want the exit doors to be short. If some post-9 idea calls for a partial rewrite of the fill logic, I would rather change three lines of my own code than negotiate with a framework's internals.

None of this is a criticism of the alternatives — vectorbt's parameter-sweep tooling in particular is impressive, and the mature choice for a production research setup is to reach for it once the harness underneath is well understood. That is a later series. This one starts from the bottom.

A Note on Code Availability

As with post 1, the finished source will live in the pine-script-strategies repository on GitHub, under the backtester/ subfolder shown in section 4. The GitHub link that normally appears in the author box is temporarily removed while the account is under a routine review; the review is expected to close in the ordinary course, at which point this post will be edited to include direct links to the module files as they are added by later posts. Until then, the tree above and the roadmap tables in this post are the complete public specification.


⚠️ Educational Disclaimer

This post is an educational architecture note for an open-source personal-use backtesting harness. It is not investment advice, financial advice, trading advice, or a recommendation to trade any specific instrument or strategy.

Any backtest produced later in this series describes past behaviour on historical data and does not guarantee future performance. Backtesting is subject to look-ahead, survivorship, overfitting, and slippage biases — this post enumerates the ones the harness explicitly refuses to hide. Trading involves substantial risk of loss and is not suitable for every investor.

Always do your own research, consult a qualified financial advisor licensed in your jurisdiction, and never risk capital you cannot afford to lose. See our full Disclaimer and Privacy Policy.

About the Author

Dongmin Park — Coder Trader author profile photo

Dongmin Park is a software engineer with over 15 years in embedded systems (automotive and defense industries) and 10+ years of active trading across Korean equities, US options, MNQ futures, and crypto. He started trading on a Kiwoom Securities account in Seoul in 2016 and now lives in Ingolstadt, Germany, after relocating in 2022.

Coder Trader is an ongoing project to document where systematic engineering discipline meets discretionary trading. Say hi on X, or email hello@codertrader.com.

Comments

Popular posts from this blog

Cumulative Volume Delta (CVD): 6 Institutional Patterns Every Trader Must Know (2 Real MNQ Examples)

Chaikin Money Flow + 200 EMA: How to Spot Institutional Accumulation (2 Real Trade Examples)

Ultimate Volume Trading Checklist: A 5-Layer Rule-Based Framework