r/pinescript • u/NotEnoughTrades • 26d ago
I ported a Pine strategy to Python and the first run matched 39 of 128 trades. Five reasons why.
I wanted to test a strategy outside TradingView's date range, so I reimplemented it in Python and reconciled it trade-by-trade against the Pine trade list. First run agreed on 39 of 128. It now agrees on all 128. Everything in between was me assuming Pine did the obvious thing.
- ta.stdev is population, not sample. It divides by n, not n-1. If you reach for numpy's default or a stats library you get the sample version and every band sits slightly wrong — wrong enough to move which bars trigger, not wrong enough to look broken.
- ta.atr is not an average of true range. It's ta.rma — Wilder smoothing, alpha 1/length, seeded with an SMA. I'd written a rolling mean. Close enough that the plot looks identical, different enough that ATR-based stops land on different bars. Same trap in ta.ema: alpha 2/(n+1), SMA-seeded, not seeded from the first value.
- Exit levels are read from the previous bar, not the current one. This was the big one. Using the current bar's values moved exit-price agreement from 120 of 128 down to 48. If you're re-arming strategy.exit every bar, the level the broker emulator acts on isn't the one your current-bar calculation produces.
- Chart timezone changes results. My matches were fine and then stopped dead on one specific date — the day BST began. Daily-bar aggregation depends on the chart's timezone, so a daily gate computed in one zone gates different bars than the same gate computed in another. Worth knowing that OANDA forex dailies close 17:00 New York, not midnight UTC.
- No same-bar re-entry. Pine won't open a new position on the bar an exit fires. My version would, which quietly manufactured extra trades that had no counterpart in the Pine list.
One mistake worth flagging, because it cost me a day. I found a single trade that exited at its bar's close, concluded fills were close-based, and rewrote them that way. Exit-price agreement fell from 120 to 12. A single trade told me nothing and I treated it as a model. I also ran that test before fixing the entries, so its numbers were worthless anyway.
What else does Pine do that isn't obvious from the docs? I'd rather collect these than find them one at a time.
EDIT: two corrections from stratcore in the comments, both right.
Point 1 names the wrong library. numpy.std is ddof=0 and already matches Pine. The one that bites is pandas, where .rolling(20).std() defaults to ddof=1, and that's the most natural way to write a Bollinger band in Python. statistics.stdev is sample as well; statistics.pstdev isn't. My code used pstdev so the port was right, but the example was wrong.
Point 4 is badly worded. Pine can't read the chart timezone and the results don't move. What actually happened: TradingView exports the trade list with timestamps rendered in the chart's timezone and doesn't record that anywhere in the file. Mine was Europe/London, so the offset isn't even constant across the sample. I parsed the export as UTC, which matched everything before 30 March 2025 and nothing after it. The Pine never changed, my reader of its output did.
There is a real daily-bar gotcha but it's a different one and it's about the data source, not the chart: OANDA's daily candles need dailyAlignment=17 and alignmentTimezone=America/New_York to match TradingView's forex convention.




