본문으로 건너뛰기

engine_trades.csv 형식 읽기

약 5분 읽기

PineForge가 공개 코퍼스의 모든 전략에 대해 쓰는 트레이드 목록, engine_trades.csv 레퍼런스입니다. 열별 의미, 행이 트레이드로 짝지어지는 방식, pandas 로더를 다룹니다.

pineforge-engine의 최신 릴리스는 2026-11-03에 나온 v1.10.0입니다.

2026-09-30에 엔진이 실제로 쓰는 형식(pineforge-engine v1.0.0 기준)에 맞춰 업데이트했습니다.

engine_trades.csv는 엔진의 트레이드 목록을 TradingView “거래 목록(List of Trades)” 내보내기와 같은 모양의 CSV로 담은 파일입니다. 그래서 두 파일을 트레이드 단위로 나란히 맞춰 볼 수 있습니다. 이 파일은 엔진 저장소의 scripts/run_strategy.py가 쓰며, 공개 pineforge-corpus의 전략 312개에는 모두 TradingView 자체 내보내기인 tv_trades.csv와 함께 이 파일이 들어 있습니다(셀프 테스트 전략 하나는 그 대신 모드별 TradingView 내보내기인 trades-<mode>.csv를 담고 있습니다). Docker 이미지와 MCP 서버는 같은 트레이드를 리포트의 trades 배열에 JSON으로 담아 돌려줍니다. 이 글은 CSV를 다룹니다.

이 파일은 무엇인가

각 행은 청산된 트레이드의 한쪽, 즉 청산 또는 진입입니다. 트레이드마다 정확히 두 행이 있고, 청산 행이 먼저 오며, 두 행은 같은 Trade #를 가집니다. 트레이드는 최신순으로 나옵니다. 파일은 가장 큰 트레이드 번호로 시작해서 1번 트레이드로 끝납니다.

열별 레퍼런스

열타입설명
Trade #정수트레이드 번호. 청산 행과 진입 행이 같은 번호를 씁니다.
Type텍스트Entry long, Exit long, Entry short, Exit short 중 하나.
Date and time텍스트UTC 기준 체결 시각. YYYY-MM-DD HH:MM 형식이며 타임존 접미사는 없습니다.
Pricefloat해당 쪽의 체결가.
Qtyfloat거래 수량. 항상 양수입니다.
Net PnLfloat트레이드의 순손익. 두 행에 같은 값이 들어갑니다.
Net PnL %floatNet PnL을 백분율로 나타낸 값. 두 행에 같은 값이 들어갑니다.
Favorable excursion USDfloat트레이드 중 가장 좋았던 미실현 이익. 0 또는 양수입니다.
Adverse excursion USDfloat트레이드 중 가장 나빴던 미실현 손실. 0 또는 음수입니다(TradingView 관례).
Cumulative PnLfloat1번 트레이드부터 이 트레이드까지 Net PnL의 누적 합계. 두 행에 같은 값이 들어갑니다.
Engine entry incarnation정수 또는 빈 값실제 진입 체결에 엔진이 붙인 id. 진입 행에 있고, 청산 행에서는 비어 있습니다.
Engine range-endopen 또는 빈 값엔진 v0.13.0 이후 버전(v1.0.0 포함)이 쓴 파일에만 있습니다. 데이터 구간이 끝나는 시점에 엔진이 청산한 포지션의 청산 행에 open이 들어갑니다.

Trade #: 짝을 맞추는 키

왕복 트레이드를 다시 구성하려면 Trade #로 묶으세요. 각 그룹에는 진입 행과 청산 행이 정확히 하나씩 있습니다. 피라미딩하는 전략은 TradingView가 번호를 매기는 방식 그대로, 진입마다 트레이드 번호를 하나씩 받습니다.

두 행 모두에 있는 Net PnL과 Cumulative PnL

트레이드의 두 행에는 모두 그 트레이드의 Net PnL, Net PnL %, Cumulative PnL이 들어 있습니다. Net PnL을 합산할 때는 트레이드마다 한 번만 세세요(예를 들어 청산 행만).

익스커션

Favorable excursion USD와 Adverse excursion USD는 포지션이 청산되기 전까지 도달한 가장 좋은 미실현 결과와 가장 나쁜 미실현 결과로, 계좌 통화 기준입니다. TradingView 내보내기와 마찬가지로 불리한 쪽 값은 0 또는 음수입니다. 두 값을 함께 보면 스톱 위치를 판단하는 데 도움이 됩니다. 이긴 트레이드의 불리한 익스커션이 작다면, 그 포지션은 한 번도 크게 밀리지 않았다는 뜻입니다.

pandas로 불러오기

이 로더는 트레이드마다 진입 행과 청산 행을 합쳐 한 행으로 만듭니다. 코퍼스 파일 하나(analyzer-anvil-percent-costs-01: 트레이드 325건, 순손익 −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}")

자주 걸리는 함정

트레이드는 한 번만 세세요. Net PnL은 트레이드의 두 행 모두에 있으므로, 열 전체를 더하면 결과가 두 배가 됩니다. 청산 행만 더하거나, 먼저 Trade #로 묶으세요.

파일은 최신순입니다. 1행은 마지막 트레이드의 청산입니다. 시간순이 필요하면 Trade #로 정렬하세요.

타임스탬프는 UTC입니다. TradingView 자체 내보내기는 차트의 타임존을 쓰므로, 시각을 비교하기 전에 둘을 맞춰야 합니다. 코퍼스의 TradingView 거래 목록에 있는 거래 시각은 Asia/Taipei(UTC+8)이며, 채점기는 전략의 inputs.json이 tv_trades_csv_tz에 다른 타임존을 지정하지 않는 한 이 타임존을 가정합니다.

끝까지 열려 있는 포지션은 청산됩니다. TradingView와 마찬가지로, 엔진은 마지막 봉 이후에도 열려 있는 포지션을 청산하므로 그 포지션은 일반 트레이드로 나타납니다(최신 엔진은 그 청산 행을 Engine range-end로 표시합니다).

TradingView 내보내기와 비교하기

TradingView의 “거래 목록(List of Trades)” 내보내기는 열이 더 많고 이름도 다릅니다. Trade number(예전 내보내기에서는 Trade #), Signal, 통화 접미사가 붙은 Price USDT, Size (qty), Net PnL USDT 등이 있고, 날짜는 차트의 타임존 기준입니다. 엔진의 채점기 scripts/verify_corpus.py처럼 열을 이름이 아니라 의미로 맞추세요. 이 채점기는 모든 표기를 받아들이고, 방향, 진입 시각(1시간 이내), 진입가로 트레이드를 짝짓습니다.

다음으로

  • 갤러리 둘러보기: 전략 페이지에서 엔진의 트레이드 목록을 TradingView의 목록과 나란히 보여 주고, 두 CSV 파일을 모두 링크합니다.
  • Claude나 Cursor에서 MCP 서버 쓰기: backtest_pine은 같은 트레이드를 JSON으로 돌려주므로 어시스턴트가 받아서 분석할 수 있습니다. 인라인으로 돌려주기에 너무 큰 리포트는 로컬 서버에서는 파일로 저장되고, 호스팅 서버에서는 다운로드 링크로 제공됩니다.