pineforge
開始使用
參考

讀懂 engine_trades.csv

PineForge 輸出的交易清單 CSV 速查:逐欄意義、成交配對如何編碼,以及約 30 行 Python 載入 pandas 範例。

約 5 分鐘閱讀#docs#csv#engine

每次 PineForge 回測都會在 JSON 摘要旁寫出 engine_trades.csv:逐筆 fill 帳——進場、出場、數量、損益與持倉期內最有利/最不利浮動。這篇逐欄說明該檔案,讓母語讀者 清楚知道每一格在說什麼,重建 round-trip 或做對齊 diff 時也不迷路。

檔案是什麼

engine_trades.csv 是一次完整回測的表格化成交列表。每行一筆 fill——進倉或平倉。Trade # 把同一趟 round-trip 的進出綁在一起。

格式刻意貼近 TradingView「成交列表」CSV。實務差異:PineForge 一律輸出 MFEMAE;TradingView 只在 Premium 給對應欄位。其餘欄名對齊。

欄位說明

欄名維持英文,與 TradingView 匯出一致,方便逐列 diff。以下說明用繁體中文。

ColumnType說明
Trade #整數round-trip 編號。進場與平倉共用同一編號。
Type字串Entry long / Exit long / Entry short / Exit short 之一。
Date and timeUTCYYYY-MM-DD HH:MM。無時區後綴。一律 UTC。
Pricefloat成交瞬間的 fill 價格(報價幣別)。
Qtyfloat成交數量。恆為正。方向由 Type 表達。
Net PnLfloat該筆平倉的損益。僅在平倉列有意義。 開倉列為空或零。
Net PnL %floatNet PnL 相對進場名義金額的百分比。
MFEfloat持倉期間最大有利偏移——平倉前曾到達的最佳浮動獲利。
MAEfloat持倉期間最大不利偏移——平倉前的最差浮動虧損。
Cumulative PnLfloat從首列至目前平倉列的 Net PnL 累加和。

Trade #

往返編號。加碼時同一編號可能出現多行;請依編號聚合,別只看列順序。

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 只看平倉列。 逐列累加會算錯。

累積損益不含未平倉。 若結束時仍有部位,改看 JSON 的 open_equity

時間戳為 UTCpd.to_datetime(..., utc=True) 較安全。

列順序 ≠ 交易邏輯順序。

跨引擎

與 TV 匯出共用欄名;比對 harness 可先檢查是否存在 MFE/MAE

下一步