MT5的EA脚本获取5分钟实时行情脚本
//+------------------------------------------------------------------+
//| Market Ticker v1.04 |
//| Sends new bar data to an external API |
//| Copyright 2025, Your Name |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Your Name"
#property link "https://your.website.com"
#property version "1.04"
#property script_show_inputs // Allows setting params from EA properties
//--- EA Inputs (外部参数,方便在加载EA时修改) ---
input string InpApiUrl = "http://127.0.0.1:8000/api/ainance/mt/receive_data"; // 您的API接口地址
input int InpTimerSeconds = 300; // 定时器触发间隔(秒),300秒 = 5分钟
//--- Global Variables (全局变量) ---
string symbols[] = {"EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCAD"}; // 您要监控的交易对列表
ENUM_TIMEFRAMES timeframe = PERIOD_M5; // 您要监控的K线周期
datetime lastBarTimes[]; // 用于存储每个交易对最后处理的K线时间
//+------------------------------------------------------------------+
//| EA初始化函数 (加载到图表时执行一次) |
//+------------------------------------------------------------------+
void OnInit()
{
// 根据交易对数量,初始化 lastBarTimes 数组
ArrayResize(lastBarTimes, ArraySize(symbols));
for(int i = 0; i < ArraySize(symbols); i++)
{
lastBarTimes[i] = 0; // 初始化为0,代表还未处理过任何K线
}
// 设置并启动定时器
EventSetTimer(InpTimerSeconds);
PrintFormat("EA 初始化成功。定时器已启动,将每 %d 秒检查一次 %s K线。", InpTimerSeconds, EnumToString(timeframe));
}
//+------------------------------------------------------------------+
//| EA终止函数 (从图表移除时执行一次) |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// 停止并销毁定时器,释放资源
EventKillTimer();
Print("EA 已终止,定时器已停止。");
}
//+------------------------------------------------------------------+
//| EA报价事件函数 (每个报价到来时执行) |
//| ★★★ 这个函数必须存在,以确保文件被识别为EA ★★★ |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 此函数被有意留空 ---
//--- 所有的核心逻辑都由 OnTimer() 事件处理 ---
}
//+------------------------------------------------------------------+
//| 定时器事件函数 (每隔 InpTimerSeconds 秒执行一次) |
//+------------------------------------------------------------------+
void OnTimer()
{
// 遍历所有交易对,检查是否有新的K线生成
for(int i = 0; i < ArraySize(symbols); i++)
{
CheckForNewBar(InpApiUrl, symbols[i], timeframe, i);
}
}
//+------------------------------------------------------------------+
//| 检查新K线并发送数据 |
//+------------------------------------------------------------------+
void CheckForNewBar(string url, string symbol, ENUM_TIMEFRAMES tf, int index)
{
MqlRates rates[];
// 尝试获取最新的2根K线数据
int copied = CopyRates(symbol, tf, 0, 2, rates);
if(copied < 1) // 如果连1根K线都获取不到,则无法继续
{
PrintFormat("无法获取 %s 的K线数据! 错误代码: %d", symbol, GetLastError());
return;
}
// 最新的K线总是在数组的第0个位置
datetime latestBarTime = rates[0].time;
// 如果最新的K线时间大于我们上次记录的时间,说明是新K线
if(latestBarTime > lastBarTimes[index])
{
PrintFormat("检测到 %s 的新K线, 时间: %s", symbol, TimeToString(latestBarTime, TIME_DATE | TIME_MINUTES));
// 更新该交易对的最后处理时间
lastBarTimes[index] = latestBarTime;
// 将这根新K线的数据转换为JSON格式
string jsonData = ConvertSingleRateToJson(rates[0], symbol, tf);
// 通过HTTP POST请求将JSON数据发送到您的API
SendSingleRateToAPI(url, jsonData);
}
}
//+------------------------------------------------------------------+
//| 将单根K线数据(MqlRates)转换为JSON字符串 |
//+------------------------------------------------------------------+
string ConvertSingleRateToJson(const MqlRates &rate, string symbol, ENUM_TIMEFRAMES tf)
{
MqlDateTime dt;
TimeToStruct(rate.time, dt);
// 将时间格式化为标准的 ISO 8601 格式 (例如: "2025-07-30T10:05:00Z")
// 这部分保持不变,因为服务器端 'time' 字段是字符串
string timeStr = StringFormat("%04d-%02d-%02dT%02d:%02d:%02dZ",
dt.year, dt.mon, dt.day,
dt.hour, dt.min, dt.sec);
// 获取交易品种的小数位数
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
string jsonData = StringFormat(
"{\"symbol\":\"%s\",\"time\":\"%s\","
"\"open\":%.*f,\"high\":%.*f,\"low\":%.*f,\"close\":%.*f,\"volume\":%d}",
symbol,
timeStr,
digits, rate.open, // "%.*f" 的第一个参数和值
digits, rate.high, // "%.*f" 的第二个参数和值
digits, rate.low, // "%.*f" 的第三个参数和值
digits, rate.close, // "%.*f" 的第四个参数和值
(long)rate.tick_volume // "%d" 的值
);
return jsonData;
}
//+------------------------------------------------------------------+
//| 将单根K线数据发送到指定的API地址 (最终釜底抽薪版) |
//+------------------------------------------------------------------+
void SendSingleRateToAPI(string url, string jsonData)
{
// 打印即将发送的 JSON 内容
Print("即将发送的 JSON: ", jsonData);
// ★★★★★ 核心修改:我们不再依赖 ArraySize-1,而是从源头根除 \0 ★★★★★
// 1. 获取纯净JSON字符串的长度(不包含任何结束符)
int jsonLength = StringLen(jsonData);
// 2. 创建一个 char 数组,其大小与纯净JSON的长度完全相等
char postData[];
ArrayResize(postData, jsonLength);
// 3. 将JSON字符串复制到 char 数组中,明确指定只复制 jsonLength 个字符。
// 这样,就没有空间留给自动添加的 \0 结束符了。
StringToCharArray(jsonData, postData, 0, jsonLength, CP_UTF8);
// ★★★★★ 修改结束 ★★★★★
// 设置HTTP请求头
string headers = "Content-Type: application/json";
int timeout = 5000; // 5秒超时
// 准备接收返回结果的变量
char result[];
string resultHeaders;
// 发起HTTP POST请求
// 现在 postData 的大小就是纯净JSON的长度,所以直接使用 ArraySize(postData)
int responseCode = WebRequest("POST", url, headers, NULL, timeout, postData, ArraySize(postData), result, resultHeaders);
if(responseCode == 200)
{
string response = (ArraySize(result) > 0) ? CharArrayToString(result, 0, -1, CP_UTF8) : "[服务器返回空内容]";
Print("数据发送成功. 后端返回: ", response);
}
else
{
PrintFormat("HTTP请求失败! 响应代码: %d, 错误代码: %d", responseCode, GetLastError());
string serverErrorResponse = (ArraySize(result) > 0) ? CharArrayToString(result, 0, -1, CP_UTF8) : "[服务器未返回任何错误详情]";
Print("服务器返回的错误详情: ", serverErrorResponse);
}
}