MT4订单历史导出脚本——完整源码
我认识的交易者里,十个人有九个在手动记账。每次平仓后就往Excel里填数据,填着填着就漏了几笔,或者干脆懒得填了。这活儿枯燥、容易出错,而且完全没必要。
这个脚本就是来解决这个问题的。拖到图表上,点一下,你的全部交易历史——包括持仓中的和平仓的——就整整齐齐地导出了一个CSV文件。然后你想导入Excel、Google Sheets还是什么分析工具,都随你。
为什么要写这个脚本
MT4自带的账户历史面板,临时看一眼还行,但真要拿来做数据分析就太难受了。没法按自定义日期范围筛选,没个导出按钮,想导出还得装第三方工具。要是你同时跑好几个EA,或者手工单和EA单混在一起,想按策略统计绩效更是无从下手。
这个脚本能帮你:
完整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,原创内容,未经授权禁止转载。
``