Reference for engine_trades.csv, the trade list PineForge writes for every strategy in its open corpus: column by column, how rows pair into trades, and a pandas loader.
Current release: pineforge-engine v1.4.0, released 2026-10-08.
Updated on 2026-09-30 to the format the engine writes (pineforge-engine v1.0.0).
engine_trades.csv is the engine's trade list in a CSV shaped like TradingView's "List
of Trades" export, so the two can be lined up trade by trade. The engine repo's
scripts/run_strategy.py writes it, and each of the 312 strategies in the public
pineforge-corpus ships one next to
TradingView's own export, tv_trades.csv (one self-test strategy ships a TradingView
export per mode instead, trades-<mode>.csv). The Docker image and the MCP servers
return the same trades as JSON, in the report's trades array; this post covers the
CSV.
What the file is
Each row is one side of a closed trade: the exit or the entry. Every trade has exactly
two rows, the exit row first, and both carry the same Trade #. Trades run newest
first: the file starts with the highest trade number and ends with trade 1.
Column-by-column reference
| Column | Type | Notes |
|---|---|---|
Trade # | integer | The trade's number, shared by its exit and entry rows. |
Type | text | Entry long, Exit long, Entry short or Exit short. |
Date and time | text | The fill time in UTC, YYYY-MM-DD HH:MM, no timezone suffix. |
Price | float | Fill price for this side. |
Qty | float | Units traded, always positive. |
Net PnL | float | The trade's net profit or loss. The same value on both rows. |
Net PnL % | float | Net PnL as a percentage. The same value on both rows. |
Favorable excursion USD | float | Best unrealized gain during the trade, zero or positive. |
Adverse excursion USD | float | Worst unrealized loss during the trade, zero or negative (TradingView's convention). |
Cumulative PnL | float | Running sum of Net PnL from trade 1 through this trade. The same value on both rows. |
Engine entry incarnation | integer or empty | The engine's id for the physical entry fill, on entry rows; empty on exit rows. |
Engine range-end | open or empty | Only in files from engine v0.13.0 and later, v1.0.0 included: open on the exit row of a position the engine closed at the end of the data range. |
Trade #: the pairing key
Group by Trade # to rebuild a round trip. Each group has exactly one entry row and one
exit row. A strategy that pyramids gets one trade number per entry, the way
TradingView numbers them.
Net PnL and Cumulative PnL on both rows
Both rows of a trade carry the trade's Net PnL, Net PnL % and Cumulative PnL. When
you sum Net PnL, count each trade once (for example, the exit rows only).
Excursions
Favorable excursion USD and Adverse excursion USD are the best and worst
unrealized results the position reached before it closed, in the account currency. The
adverse value is zero or negative, as in TradingView's export. Together they help judge stop
placement: a small adverse excursion on a winning trade means the position was never
squeezed.
Loading into pandas
This loader joins each trade's entry and exit rows into one row per trade. It was
checked against a corpus file (analyzer-anvil-percent-costs-01: 325 trades, net P&L
−2,817.54).
import pandas as pd
def load_trades(path: str) -> pd.DataFrame:
df = pd.read_csv(path)
# Times are UTC, formatted "YYYY-MM-DD HH:MM", with no timezone suffix
df["Date and time"] = pd.to_datetime(df["Date and time"], utc=True)
entries = df[df["Type"].str.startswith("Entry")].rename(columns={
"Date and time": "entry_dt",
"Price": "entry_price",
"Type": "direction",
})
entries["direction"] = entries["direction"].str.replace("Entry ", "")
exits = df[df["Type"].str.startswith("Exit")].rename(columns={
"Date and time": "exit_dt",
"Price": "exit_price",
"Net PnL": "net_pnl",
"Net PnL %": "net_pnl_pct",
"Favorable excursion USD": "mfe",
"Adverse excursion USD": "mae",
})
# One entry row and one exit row per Trade #
trades = entries[["Trade #", "direction", "entry_dt", "entry_price", "Qty"]].merge(
exits[["Trade #", "exit_dt", "exit_price", "net_pnl", "net_pnl_pct", "mfe", "mae"]],
on="Trade #",
)
return trades.sort_values("Trade #").reset_index(drop=True)
if __name__ == "__main__":
trades = load_trades("engine_trades.csv")
print(trades[["Trade #", "direction", "entry_dt", "exit_dt", "net_pnl", "mfe", "mae"]]
.head(5)
.to_string(index=False))
print(f"\nTotal trades: {len(trades)}")
print(f"Win rate: {(trades['net_pnl'] > 0).mean():.1%}")
print(f"Net P&L: {trades['net_pnl'].sum():.2f}")
Common gotchas
Count each trade once. Net PnL sits on both rows of a trade, so summing the whole
column doubles the result. Sum the exit rows, or group by Trade # first.
The file runs newest first. Row 1 is the exit of the last trade. Sort by Trade # if
you need chronological order.
Timestamps are UTC. TradingView's own export uses your chart's timezone, so line the
two up before comparing times. Trade times in the corpus's TradingView trade lists are in
Asia/Taipei (UTC+8), and the grader assumes that unless a strategy's inputs.json
names another zone in tv_trades_csv_tz.
Positions open at the end are closed. Like TradingView, the engine closes a position
that is still open after the last bar, so it appears as a normal trade (current engines
flag its exit row in Engine range-end).
Comparing with TradingView's export
TradingView's "List of Trades" export has more columns and different names: Trade number (older exports: Trade #), Signal, a currency-suffixed Price USDT, Size (qty), Net PnL USDT and so on, with dates in your chart's timezone. Match columns by
meaning, not by name, as the engine's grader scripts/verify_corpus.py does: it accepts
every spelling and pairs trades by direction, entry time (within an hour) and entry
price.
Where to go next
- Browse the gallery: strategy pages show the engine's trade list next to TradingView's and link both CSV files.
- Use the MCP servers from Claude or Cursor:
backtest_pinereturns the same trades as JSON for your assistant to analyze; a report too large to return inline is saved to a file (local server) or linked for download (hosted server).