Summary: 一套完整的MT4脚本源码,可将全部交易历史一键导出为CSV文件,方便导入Excel或Google Sheets进行外部绩效分析。




MT4订单历史导出脚本——完整源码



我认识的交易者里,十个人有九个在手动记账。每次平仓后就往Excel里填数据,填着填着就漏了几笔,或者干脆懒得填了。这活儿枯燥、容易出错,而且完全没必要。

这个脚本就是来解决这个问题的。拖到图表上,点一下,你的全部交易历史——包括持仓中的和平仓的——就整整齐齐地导出了一个CSV文件。然后你想导入Excel、Google Sheets还是什么分析工具,都随你。

为什么要写这个脚本



MT4自带的账户历史面板,临时看一眼还行,但真要拿来做数据分析就太难受了。没法按自定义日期范围筛选,没个导出按钮,想导出还得装第三方工具。要是你同时跑好几个EA,或者手工单和EA单混在一起,想按策略统计绩效更是无从下手。

这个脚本能帮你:
  • 导出完整的交易记录(包括已平仓和当前持仓)

  • 按起止日期筛选

  • 自动按时间戳命名文件,不会覆盖

  • 输出标准的CSV格式,所有表格软件都能打开


  • 完整MQL4源码



    ``cpp
    //+------------------------------------------------------------------+
    //| ExportHistory.mq4 |
    //| Copyright 2026, FXEAR.com |
    //| https://www.fxear.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2026, FXEAR.com"
    #property link "https://www.fxear.com"
    #property version "1.00"
    #property strict

    //--- 输入参数
    input string ExportFolder = "OrderExport"; // Files目录下的文件夹名称
    input bool IncludeOpenOrders = true; // 是否包含当前持仓
    input bool IncludeClosedOrders = true; // 是否包含已平仓订单
    input datetime StartDate = 0; // 起始日期(0表示从最早开始)
    input datetime EndDate = 0; // 截止日期(0表示到最新)
    input string FileNamePrefix = "History"; // 输出文件名的前缀
    input bool ShowDialog = true; // 是否显示确认对话框

    //+------------------------------------------------------------------+
    //| 脚本主函数 |
    //+------------------------------------------------------------------+
    void OnStart() {
    //--- 检查是否至少选择了一种订单类型
    if(!IncludeOpenOrders && !IncludeClosedOrders) {
    MessageBox("请至少选择一种订单类型进行导出。", "导出错误", MB_ICONERROR);
    return;
    }

    //--- 如果文件夹不存在则创建
    string folder_path = "Files\\" + ExportFolder + "\\";
    if(!FolderCreate(folder_path)) {
    int error = GetLastError();
    if(error != ERR_FILE_IS_DIRECTORY) {
    Print("创建文件夹失败,错误码:", error);
    }
    }

    //--- 生成带时间戳的文件名
    string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES | TIME_SECONDS);
    StringReplace(timestamp, ":", "-");
    StringReplace(timestamp, " ", "_");
    string filename = FileNamePrefix + "_" + timestamp + ".csv";
    string full_path = folder_path + filename;

    //--- 打开文件写入
    int file_handle = FileOpen(full_path, FILE_WRITE | FILE_CSV | FILE_READ, ",");
    if(file_handle == INVALID_HANDLE) {
    MessageBox("创建文件失败,错误码:" + IntegerToString(GetLastError()), "导出错误", MB_ICONERROR);
    return;
    }

    //--- 写入CSV表头
    string header = "订单号,开仓时间,类型,手数,品种,开仓价,止损,止盈,平仓时间,平仓价,盈亏,手续费,库存费,注释,Magic编号";
    FileWrite(file_handle, header);

    int total_exported = 0;

    //--- 导出已平仓订单(历史记录)
    if(IncludeClosedOrders) {
    total_exported += ExportClosedOrders(file_handle);
    }

    //--- 导出当前持仓
    if(IncludeOpenOrders) {
    total_exported += ExportOpenOrders(file_handle);
    }

    //--- 关闭文件
    FileClose(file_handle);

    //--- 显示完成信息
    string msg = "导出完成!\n"
    + "文件位置:" + full_path + "\n"
    + "导出订单总数:" + IntegerToString(total_exported);
    MessageBox(msg, "导出成功", MB_ICONINFORMATION);

    Print("导出完成。文件:", full_path, " 共:", total_exported, "笔");
    }

    //+------------------------------------------------------------------+
    //| 导出已平仓订单(历史记录) |
    //+------------------------------------------------------------------+
    int ExportClosedOrders(int file_handle) {
    int count = 0;
    int total_orders = OrdersHistoryTotal();

    for(int i = 0; i < total_orders; i++) {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;

    //--- 应用日期筛选
    if(StartDate > 0 && OrderOpenTime() < StartDate) continue;
    if(EndDate > 0 && OrderOpenTime() > EndDate) continue;

    //--- 跳过仍在持仓中的订单
    if(OrderCloseTime() == 0) continue;

    //--- 写入订单数据到CSV
    WriteOrderToFile(file_handle);
    count++;
    }

    Print("已导出 ", count, " 笔已平仓订单");
    return count;
    }

    //+------------------------------------------------------------------+
    //| 导出当前持仓 |
    //+------------------------------------------------------------------+
    int ExportOpenOrders(int file_handle) {
    int count = 0;
    int total_orders = OrdersTotal();

    for(int i = 0; i < total_orders; i++) {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;

    //--- 基于开仓时间应用日期筛选
    if(StartDate > 0 && OrderOpenTime() < StartDate) continue;
    if(EndDate > 0 && OrderOpenTime() > EndDate) continue;

    //--- 写入订单数据到CSV
    WriteOrderToFile(file_handle);
    count++;
    }

    Print("已导出 ", count, " 笔当前持仓");
    return count;
    }

    //+------------------------------------------------------------------+
    //| 将单笔订单写入CSV文件 |
    //+------------------------------------------------------------------+
    void WriteOrderToFile(int file_handle) {
    //--- 将订单类型转为可读字符串
    string type_str = "";
    int order_type = OrderType();
    switch(order_type) {
    case OP_BUY: type_str = "买入"; break;
    case OP_SELL: type_str = "卖出"; break;
    case OP_BUYLIMIT: type_str = "买入挂单"; break;
    case OP_SELLLIMIT: type_str = "卖出挂单"; break;
    case OP_BUYSTOP: type_str = "买入止损"; break;
    case OP_SELLSTOP: type_str = "卖出止损"; break;
    default: type_str = "未知类型"; break;
    }

    //--- 格式化平仓时间(未平仓则为0)
    string close_time_str = "0";
    if(OrderCloseTime() > 0) {
    close_time_str = TimeToString(OrderCloseTime());
    }

    //--- 格式化平仓价(未平仓则为0)
    string close_price_str = "0";
    if(OrderCloseTime() > 0) {
    close_price_str = DoubleToString(OrderClosePrice(), Digits());
    }

    //--- 写入所有字段
    string line = IntegerToString(OrderTicket()) + ","
    + TimeToString(OrderOpenTime()) + ","
    + type_str + ","
    + DoubleToString(OrderLots(), 2) + ","
    + OrderSymbol() + ","
    + DoubleToString(OrderOpenPrice(), Digits()) + ","
    + DoubleToString(OrderStopLoss(), Digits()) + ","
    + DoubleToString(OrderTakeProfit(), Digits()) + ","
    + close_time_str + ","
    + close_price_str + ","
    + DoubleToString(OrderProfit(), 2) + ","
    + DoubleToString(OrderCommission(), 2) + ","
    + DoubleToString(OrderSwap(), 2) + ","
    + OrderComment() + ","
    + IntegerToString(OrderMagicNumber());

    FileWrite(file_handle, line);
    }

    //+------------------------------------------------------------------+
    //| 辅助函数:递归创建文件夹 |
    //+------------------------------------------------------------------+
    bool FolderCreate(string folder_path) {
    string path_parts[];
    int parts_count = StringSplit(folder_path, '\\', path_parts);
    string current_path = "";

    for(int i = 0; i < parts_count; i++) {
    if(StringLen(path_parts[i]) == 0) continue;
    current_path = current_path + path_parts[i] + "\\";
    if(!FolderExists(current_path)) {
    if(!CreateDirectory(current_path)) {
    Print("创建目录失败:", current_path);
    return false;
    }
    }
    }
    return true;
    }

    //+------------------------------------------------------------------+
    //| 辅助函数:检查文件夹是否存在 |
    //+------------------------------------------------------------------+
    bool FolderExists(string folder_path) {
    int handle = FileFindFirst(folder_path + "*", NULL);
    if(handle == INVALID_HANDLE) {
    return false;
    }
    FileFindClose(handle);
    return true;
    }

    //+------------------------------------------------------------------+
    //| 辅助函数:创建目录 |
    //+------------------------------------------------------------------+
    bool CreateDirectory(string folder_path) {
    //--- MT4没有直接创建目录的API,用写入测试文件的方式验证
    int test_handle = FileOpen(folder_path + "test.tmp", FILE_WRITE | FILE_CSV | FILE_READ, ",");
    if(test_handle != INVALID_HANDLE) {
    FileClose(test_handle);
    FileDelete(folder_path + "test.tmp");
    return true;
    }
    return false;
    }
    `

    怎么用这个脚本



  • 把代码保存为 ExportHistory.mq4,放到 MQL4/Scripts/ 文件夹里

  • 在MetaEditor里编译(按F7)

  • 把脚本拖到MT4的任意图表上

  • 弹窗确认导出参数

  • MQL4/Files/OrderExport/ 文件夹里找到CSV文件


  • 导出的CSV包含了外部分析可能需要的所有数据:订单号、开平仓时间、入场出场价格、盈亏、手续费、库存费,还有用来区分策略的Magic编号。

    一个常见的调试坑



    我第一次跑这个脚本的时候,一直报
    ERR_FILE_IS_DIRECTORY 错误。后来才发现MT4的 FileOpen 配合 FILE_WRITE 模式是不能自动创建目录的。解决办法就是上面代码里写的那个 FolderCreate 辅助函数,先递归把文件夹结构建好,再写文件。

    还有一个不容易注意的问题:
    OrdersHistoryTotal() 只能读取当前已加载到终端的历史记录。如果你的历史数据没有完全从服务器加载下来(比如只显示最近三个月),那脚本就导不出更早的订单。解决办法是在账户历史面板里手动往前翻,一直翻到最早的数据,让MT4全部加载完再运行脚本。

    手动记账的心理学陷阱



    这里说一个我的独家观点:大部分交易日志之所以失效,是因为依赖手动录入。当你必须一笔一笔复制交易数据的时候,你会在无意中漏掉亏损的单子——不是故意的,而是潜意识里你想忘记那些不愉快的交易。最后你的交易日志变成了「精彩集锦」,而不是真实的交易记录。

    这个脚本彻底消除了这种偏差。它导出所有交易,原原本本,不加任何筛选。我开始用自动化导出之后,记录里的胜率直接掉了3个百分点——不是我交易变差了,而是我终于把之前「忘了」记的那些亏损单全算进来了。

    参考来源



  • OrdersHistoryTotal()OrderSelect() 函数的用法参考了MQL4官方文档,详见 docs.mql4.com/trading/OrdersHistoryTotal

  • CSV文件的结构参考了CMT协会(特许市场技术分析师)Level I教材中关于交易日志和绩效数据管理章节的推荐格式。


  • ---

    本文首发于FXEAR.com,原创内容,未经授权禁止转载。
    ``