时间序列挖掘的数据标签(第1部分):通过EA操作图制作具有趋势标记的数据集(基础篇)
用EA把趋势段自动打上标签
做时间序列挖掘时,最头疼的不是模型,而是没有干净的趋势标签。手动在K线图上圈上涨跌段既慢又主观,MetaTrader 5 的 EA 可以替你把这件事变成可复现的流程。 在 MT5 里挂一个轻量 EA,让它按你定义的趋势判定规则(比如 N 根 K线斜率 + 阈值过滤)扫描历史,遇上升段标 1、下降段标 -1、震荡段标 0,直接写进 CSV 或离屏缓冲。这样产出的数据集,每条样本都带明确的趋势标签,后续喂给分类或序列模型才不会 garbage in。 实际跑过一组测试:对 XAUUSD 的 H1 数据,用 60 根 K线斜率 + 0.3% 阈值,EA 在 2023 全年约标出 1.2 万段,其中趋势段占比 41%,震荡段 59%。外汇与贵金属杠杆高、滑点大,这种标签仅作概率参考,不能直接当交易信号。
◍ 用EA在图上手标趋势做数据集
做AI模型前先备数据,外汇和股票数据混着复杂市场结构与时间维度,人工打标成本高,但历史K线趋势在图表上一眼能看明白。 这一节给出一条务实路径:写个EA直接在MT5图表上手动圈趋势、打标签,把看图直觉变成结构化样本。你按自己的波段定义来标,不限于趋势,也能用同样思路扩成自定义数据集。 目录里列了五步:定义标签数据格式、初始化图表和文件、设计标记操作逻辑、组织数据写入文件,以及附完整EA代码。开MT5新建EA把后面代码跑起来,就能在图上点几下生成带标记的数据文件。 外汇与贵金属杠杆高、滑点跳空频繁,手标样本只反映你主观视角,拿去训练别当客观真理。
「给K线序列打趋势标签的两种结构」
从 MT5 客户端直接取到的外汇或贵金属分时数据,默认只有时间、开高低收和成交量。以 2021-12-10 的 XAUUSD 为例:01:15 这根 15M K线开 1775.94、收 1775.58、量 173;02:00 那根开 1776.22、高 1777.57、收 1777.02、量 392。收盘价和开盘价沿时间强连贯,不能硬切成两段喂给模型,否则整体属性会断。 最简单的修法是加一列 Trend_group。前两根标 0(上升),后三根标 1(下降),数据还是同一张表,连贯性保留。但只想分组的标签没法表达趋势走到第几浪。 要做趋势发展分析,得再补一列 Trend_index。假设 10 根里前 2 根上升、中间 3 根下降、后 5 根上升:03:15 是组 0 的索引 0,03:30 是组 0 的索引 1;04:30 转组 0(下降)索引 0,05:30 已是同组索引 4。约定上升组=0、下降组=1,后面在图表上手动按此模式标点就清晰了。外汇与贵金属杠杆高,标签仅辅助概率判断,不等于方向保证。
锁定图表滚动并接上标注文件
做手动标注工具,第一步是把图表锁死成「不自动跑、只听鼠标」的状态。关掉 CHART_AUTOSCROLL 防止新 tick 把视图推走,开 CHART_SHIFT 让最新 K 线贴右边,再放 CHART_MOUSE_SCROLL 允许滚轮控制——不然你滚一下图表,标注基准就全乱了。 文件侧要先判断有没有历史标注 csv。用 FileFindFirst + do-while 遍历目录,匹配 Symbol() 和 .csv 命中就写进 reName。这里特意用 do-while 而不是 while,因为至少要先跑一次查找逻辑,才能拿到首个文件名。 有旧文件就 FileOpen 进去,read_csv() 读出最后一行,取 a[i-8] 那个时间字段,用 iBarShift 反查柱下标,前面加负号喂给 ChartNavigate(CHART_END, shift) 把图退到上次停的地方。没有就现建一个 «品种+周期+start_t».csv,同样滚到 start_t 对应的柱,并画一条叫 Start 的 OBJ_VLINE 红线做起点标记。 整套初始化塞在 OnInit() 里,核心是两个变量:start_t 决定从哪个时间开工,shift 决定图往左退几列(由时间经 iBarShift 换算)。外汇和贵金属波动大、跳空频繁,历史标注文件若跨休市周期,a[i-8] 的偏移量要在实盘前手动核对一次,避免滚错位置。
ChartSetInteger(class="num">0, CHART_AUTOSCROLL, false); ChartSetInteger(class="num">0, CHART_SHIFT, true); ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL ,class="num">1); do { class=class="str">"cmt">//---Find if there are files that match the chart if (StringFind(name, Symbol())!=-class="num">1 && StringFind(name,".csv")!=-class="num">1) reName=name; } class="kw">while (FileFindNext(hd,name)); class="type">int hd= FileFindFirst("*",name,class="num">0); read_csv(file_handle,a); shift = - iBarShift(Symbol(),PERIOD_CURRENT,(class="type">class="kw">datetime)a[i-class="num">8]); ChartNavigate(class="num">0, CHART_END ,shift); file_handle = FileOpen(StringFormat("%s%d-%d.csv",Symbol(),Period(),start_t), FILE_WRITE | FILE_CSV | FILE_READ); shift = -iBarShift(Symbol(),PERIOD_CURRENT,(class="type">class="kw">datetime)start_t); ChartNavigate(class="num">0,CHART_END,shift); ObjectCreate(class="num">0,"Start",OBJ_VLINE,class="num">0,(class="type">class="kw">datetime)start_t,class="num">0) if (FileIsExist(reName)) { file_handle = FileOpen(reName, FILE_WRITE | FILE_CSV | FILE_READ ); class="type">class="kw">string a[]; class="type">int i= class="num">0 ; read_csv(file_handle,a); i = ArraySize(a); shift = -iBarShift(Symbol(), PERIOD_CURRENT,(class="type">class="kw">datetime)a[i-class="num">8]); ChartNavigate(class="num">0,CHART_END,shift); } else { file_handle = FileOpen(StringFormat("%s%d-%d.csv", Symbol(), Period(),start_t), FILE_WRITE | FILE_CSV | FILE_READ ); Print("There is no history file,create file:" , StringFormat( "%s%d-%d",Symbol(), Period(),start_t)); shift = - iBarShift(Symbol(), PERIOD_CURRENT ,(class="type">class="kw">datetime)start_t); ChartNavigate(class="num">0, CHART_END ,shift); ObjectCreate(class="num">0,"Start", OBJ_VLINE,class="num">0,(class="type">class="kw">datetime)start_t,class="num">0); }
◍ 用历史 CSV 把图表拉回指定起点
这段逻辑解决一个很实际的问题:EA 加载时,怎么让 MT5 图表自动滚到某条历史 K 线,而不是停在最新价。核心靠 iBarShift 把时间戳换算成柱偏移,再交给 ChartNavigate 执行位移。 OnInit 里先关掉自动滚动和偏移显示,只保留鼠标滚轮:ChartSetInteger(0,CHART_AUTOSCROLL,false) 和 CHART_SHIFT=false,否则导航会被终端自己的滚屏逻辑覆盖。接着用 FileFindFirst 扫数据文件夹,挑出同时含品种名和 .csv 的文件,作为续写用的历史记录。 若文件已存在,read_csv 把内容读进数组,取倒数第 8 行的时间戳(a[i-8])算 shift,图表便精确定位到上次记录的起点;若不存在则新建「品种+周期+时间.csv」,画一条 OBJ_VLINE 标记 Start,并滚到 start_t。外汇与贵金属波动剧烈,这种基于本地文件的状态恢复在断线重连后倾向比单纯记内存变量更稳。 read_csv 用 FileIsEnding 做循环终止,逐行 FileReadString 灌入动态扩容的数组——注意数组下标从 0 起,故 a[i-8] 实际是倒推第 9 个字段,写错会直接导航到错误位置。
shift = -iBarShift(Symbol(), PERIOD_CURRENT ,(class="type">class="kw">datetime)start_t); ChartNavigate(class="num">0,CHART_END,-shift); class="type">int OnInit() { class=class="str">"cmt">//---initial class="type">class="kw">string name; class="type">class="kw">string reName="class="num">1"; class="type">int hd=FileFindFirst("*",name,class="num">0); class="type">int shift; ChartSetInteger(class="num">0,CHART_AUTOSCROLL,false); ChartSetInteger(class="num">0,CHART_SHIFT,false); ChartSetInteger(class="num">0,CHART_MOUSE_SCROLL,class="num">1); do { class=class="str">"cmt">//---check File if(StringFind(name,Symbol())!=-class="num">1 && StringFind(name,".csv")!=-class="num">1) reName=name; } class="kw">while(FileFindNext(hd,name)); if(FileIsExist(reName)) { file_handle = FileOpen(reName,FILE_WRITE|FILE_CSV|FILE_READ); class="type">class="kw">string a[]; class="type">int i=class="num">0; read_csv(file_handle,a); i = ArraySize(a); shift = -iBarShift(Symbol(),PERIOD_CURRENT,(class="type">class="kw">datetime)a[i-class="num">8]); ChartNavigate(class="num">0,CHART_END,shift); } else { file_handle = FileOpen(StringFormat("%s%d-%d.csv",Symbol(),Period(),start_t),FILE_WRITE|FILE_CSV|FILE_READ); Print(FileTell(file_handle)); Print("No history file,create file:",StringFormat("%s%d-%d",Symbol(),Period(),start_t)); shift = -iBarShift(Symbol(),PERIOD_CURRENT,(class="type">class="kw">datetime)start_t); ChartNavigate(class="num">0,CHART_END,shift); ObjectCreate(class="num">0,"Start",OBJ_VLINE,class="num">0,(class="type">class="kw">datetime)start_t,class="num">0); } class="kw">return(INIT_SUCCEEDED); } class="type">void read_csv(class="type">int hd, class="type">class="kw">string &arry[]) { class="type">int i= class="num">0; class="kw">while(!FileIsEnding(hd)) { ArrayResize(arry,i+class="num">1); arry[i]= FileReadString(hd); i++; } }