MQL5 CSV 交易日誌:自動記錄每筆交易到檔案

📌 本文重點
學習用 MQL5 的檔案操作函數建立完整的交易日誌系統,自動將每筆交易記錄為 CSV 格式,方便用 Excel 分析績效。

MQL5 檔案操作基礎

// 檔案路徑:MT5 的 MQL5/Files/ 目錄下
// 可在 MT5「文件」→「開啟數據資料夾」→「MQL5/Files」找到

void WriteTestFile()
{
    // 開啟(或建立)CSV 檔案,附加模式(不覆蓋舊數據)
    int fileHandle = FileOpen("trade_log.csv",
                              FILE_WRITE | FILE_READ | FILE_CSV | FILE_ANSI,
                              ',');  // 分隔符

    if (fileHandle == INVALID_HANDLE)
    {
        Print("無法開啟檔案!錯誤: ", GetLastError());
        return;
    }

    // 移動到檔案末尾(附加模式)
    FileSeek(fileHandle, 0, SEEK_END);

    // 寫入一行(逗號分隔)
    FileWrite(fileHandle,
              TimeToString(TimeCurrent()),  // 時間
              _Symbol,                       // 品種
              "BUY",                         // 方向
              "0.10",                        // 手數
              "1.08500",                     // 進場價
              "1.08000",                     // 止損
              "1.09500",                     // 止盈
              "55.20"                        // 盈虧
    );

    FileClose(fileHandle);
    Print("數據已寫入 trade_log.csv");
}

完整交易日誌系統

class CTradeLogger
{
private:
    string m_filename;
    bool   m_headerWritten;

    void EnsureHeader()
    {
        if (m_headerWritten) return;

        // 檢查檔案是否存在
        bool fileExists = FileIsExist(m_filename, FILE_COMMON);
        m_headerWritten = fileExists; // 已存在則假設標頭已寫

        if (!fileExists)
        {
            int fh = FileOpen(m_filename, FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_ANSI, ',');
            if (fh == INVALID_HANDLE) return;
            // 寫入標頭
            FileWrite(fh, "Time","Symbol","Direction","Lots","OpenPrice",
                         "ClosePrice","SL","TP","Profit","Duration","MagicNo","Comment");
            FileClose(fh);
            m_headerWritten = true;
        }
    }

public:
    CTradeLogger(string filename = "TradeLog.csv")
        : m_filename(filename), m_headerWritten(false) {}

    void LogTrade(string symbol, string dir, double lots,
                  double openPx, double closePx, double sl, double tp,
                  double profit, int durationMin, long magic, string comment)
    {
        EnsureHeader();
        int fh = FileOpen(m_filename, FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_ANSI, ',');
        if (fh == INVALID_HANDLE) { Print("日誌寫入失敗"); return; }

        FileSeek(fh, 0, SEEK_END);
        int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
        FileWrite(fh,
            TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
            symbol, dir,
            DoubleToString(lots, 2),
            DoubleToString(openPx,  digits),
            DoubleToString(closePx, digits),
            DoubleToString(sl,      digits),
            DoubleToString(tp,      digits),
            DoubleToString(profit,  2),
            IntegerToString(durationMin),
            IntegerToString(magic),
            comment
        );
        FileClose(fh);
    }
};

// 全域日誌物件
CTradeLogger g_logger;

// 在 OnTradeTransaction 中自動記錄平倉
void OnTradeTransaction(const MqlTradeTransaction &trans,
                         const MqlTradeRequest &req,
                         const MqlTradeResult &res)
{
    // 只記錄持倉關閉事件
    if (trans.type != TRADE_TRANSACTION_DEAL_ADD) return;
    if (trans.deal_type != DEAL_TYPE_BUY && trans.deal_type != DEAL_TYPE_SELL) return;

    // 從歷史中取得交易詳情
    if (!HistoryDealSelect(trans.deal)) return;
    long   entry  = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
    if (entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT) return; // 只記錄平倉

    string symbol  = HistoryDealGetString(trans.deal,  DEAL_SYMBOL);
    double profit  = HistoryDealGetDouble(trans.deal,  DEAL_PROFIT);
    double lots    = HistoryDealGetDouble(trans.deal,  DEAL_VOLUME);
    double price   = HistoryDealGetDouble(trans.deal,  DEAL_PRICE);
    long   magic   = HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
    string comment = HistoryDealGetString(trans.deal,  DEAL_COMMENT);
    int    type    = (int)HistoryDealGetInteger(trans.deal, DEAL_TYPE);

    g_logger.LogTrade(
        symbol,
        (type == DEAL_TYPE_SELL) ? "BUY_CLOSE" : "SELL_CLOSE",
        lots, 0, price, 0, 0, profit, 0, magic, comment
    );
    Print("交易已記錄:", symbol, " 盈虧=$", profit);
}

讀取 CSV 進行統計

void PrintTradeSummary()
{
    int fh = FileOpen("TradeLog.csv", FILE_READ|FILE_CSV|FILE_COMMON|FILE_ANSI, ',');
    if (fh == INVALID_HANDLE) return;

    FileReadString(fh); // 跳過標頭行
    int    totalTrades = 0, winners = 0;
    double totalProfit = 0, totalWin = 0, totalLoss = 0;

    while (!FileIsEnding(fh))
    {
        for (int col = 0; col < 8; col++) FileReadString(fh); // 跳過前幾列
        double profit = StringToDouble(FileReadString(fh));    // 第9列 = 盈虧
        for (int col = 0; col < 3; col++) FileReadString(fh); // 跳過剩餘列

        totalTrades++;
        totalProfit += profit;
        if (profit > 0) { winners++; totalWin  += profit; }
        else              { totalLoss += MathAbs(profit); }
    }
    FileClose(fh);

    double winRate = totalTrades > 0 ? (double)winners/totalTrades*100 : 0;
    double pf      = totalLoss > 0   ? totalWin/totalLoss : 0;

    Print("=== 交易統計 ===");
    Print("總交易數: ", totalTrades, " | 勝率: ", DoubleToString(winRate,1), "%");
    Print("總獲利: $", DoubleToString(totalProfit,2));
    Print("利潤因子: ", DoubleToString(pf,2));
}

本文由 James Lee 撰寫。內容僅供教育目的,不構成投資建議。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *