engine_trades.csv 형식 읽기
PineForge가 내보내는 트레이드 목록 CSV 완전 레퍼런스. 열별 의미, 트레이드 페어 인코딩, pandas로 불러오는 30줄 내외 Python 스니펫.
PineForge 백테스트는 JSON 요약과 함께 engine_trades.csv를 남깁니다. 엔진이 본 모든 fill을 행 단위로 적은 원장—진입·청산·수량·PnL·트레이드 내 최대 유리/불리 폭입니다. 이 글은 각 열이 정확히 무엇을 뜻하는지 한국어로 짚는 참조이며, 왕복을 재구성하거나 패리티 diff를 할 때 헷갈리지 않게 하려는 목적입니다.
파일이 하는 일
engine_trades.csv는 한 번의 백테스트가 끝난 뒤의 표 형식 트레이드 목록입니다. 각 행은 한 번의 fill—포지션 진입이나 청산. Trade #로 진입과 청산을 묶어 왕복을 복원합니다.
형식은 TradingView «List of Trades» 내보내기를 의도적으로 닮았습니다. 차이는 PineForge가 항상 MFE와 MAE를 넣는다는 점—TV는 Premium에서만 해당 열을 줍니다. 나머지 헤더는 맞춰 두었습니다.
열별 설명
열 이름은 TradingView 내보내기와 줄 단위 diff를 맞추기 위해 영어로 둡니다. 설명만 한국어입니다.
Trade #
왕복 ID. 피라미딩이면 같은 번호가 두 줄 이상 나올 수 있습니다. 이 키로 묶어 읽으세요.
Type
네 가지 문자열만. 홀드 행은 없습니다.
Net PnL
청산 행에만 의미가 있습니다.
MFE / MAE
보유 중 만족한 최대 이익·최대 손실(미실현 기준). 스톱 품질을 볼 때 씁니다.
파일 걷기
Trade #로 그룹화하는 게 기본입니다. 행 순서는 체결 시각 순이라 트레이드 번호 순이 아닐 수 있습니다.
pandas로 읽기
import pandas as pd
def load_trades(path: str) -> pd.DataFrame:
df = pd.read_csv(path)
# Parse timestamps; UTC, no tz suffix in the file
df["Date and time"] = pd.to_datetime(df["Date and time"], utc=True)
entries = df[df["Type"].str.startswith("Entry")].copy()
exits = df[df["Type"].str.startswith("Exit")].copy()
entries = entries.rename(columns={
"Date and time": "entry_dt",
"Price": "entry_price",
"Qty": "entry_qty",
"Type": "direction",
})
entries["direction"] = entries["direction"].str.replace("Entry ", "")
exits = exits.rename(columns={
"Date and time": "exit_dt",
"Price": "exit_price",
"Net PnL": "net_pnl",
"Net PnL %": "net_pnl_pct",
"MFE": "mfe",
"MAE": "mae",
})
# Join on Trade # (take last exit for pyramiding strategies)
exits_agg = exits.groupby("Trade #").last().reset_index()
entries_agg = entries.groupby("Trade #").first().reset_index()
trades = entries_agg.merge(exits_agg[
["Trade #", "exit_dt", "exit_price", "net_pnl", "net_pnl_pct", "mfe", "mae"]
], on="Trade #")
return trades
if __name__ == "__main__":
trades = load_trades("engine_trades.csv")
print(trades[["Trade #", "direction", "entry_dt", "exit_dt", "net_pnl", "mfe", "mae"]]
.head(10)
.to_string(index=False))
print(f"\nTotal trades: {len(trades)}")
print(f"Win rate: {(trades['net_pnl'] > 0).mean():.1%}")
print(f"Avg MFE: {trades['mfe'].mean():.4f}")
print(f"Avg MAE: {trades['mae'].mean():.4f}")자주 걸리는 것
Net PnL은 청산 행. 행을 순서대로 더하면 깨집니다.
Cumulative PnL은 미청산 제외. 백테스트 끝에 포지션이 남으면 JSON의 open_equity를 보세요.
타임스탬프는 UTC.
행 순서 ≠ 트레이드 순서.
크로스 엔진
TV 내보내기와 기본 헤더는 같습니다. MFE/MAE는 TV 플랜에 따라 없을 수 있고 PineForge는 항상 냅니다.