使用 OpenCL 测试烛形形态·进阶篇
(2/3)· 当 MQL5 单线程回测针杆吞噬形态卡在小时级,OpenCL 能否把优化周期砍到分钟级
很多交易者把 OpenCL 当成矩阵和神经网络专属,面对烛形形态回测这种"小任务"宁可让策略测试器慢慢跑。结果在 EURUSD 上优化两个参数,一晚上过去进度条还没满。本文承接基础篇的 OpenCL 环境铺垫,直接把形态搜索与订单转移丢进内核并行算。
「用 OpenCL 把形态测试搬出 MT5 主线程」
Test() 方法本质是对 test() 的封装:入口接日期范围、参数与结果结构引用,成功返回 true 并写回 result,出错返回 false,详细错误靠 GetLastErrorExt() 取。内部先上传时间序列、初始化 OpenCL 对象与内核,正常才进 test();退出时必须做终止化并释放序列缓冲区,否则 MT5 内存会悄悄漏。 test() 里第一步就用 LoadTimeseriesOCL() 把序列灌进 OpenCL 缓冲。接着跑 find_patterns() 内核(对应 k_FIND_PATTERNS),订单缓冲区大小设为当前周期柱数两倍——先假设每根柱都能命中形态,虽看着荒唐,但后续加形态时能少踩坑。 Execute() 返回不代表内核跑完,可能还在排队。查状态用 CLExecutionStatus(),要等结果就轮询或直接在 BufferRead() 里阻塞等。count[0] 存形态数,order_to_M1() 内核在 count[1] 记 M1 入场数,两者相等才算过。 实测里自建 OpenCL 测试器与 MT5 内建比:交易数一致,净利润内建 1000.50、自写 1007.99。差值来自没算隔夜息、用了 1 分钟 OHLC 近似。全程仅占 16MB 内存,初始化最耗时,总处理 376 毫秒,和内置几乎持平;但才 200 笔交易,准备开销摊不薄,要几百倍订单才显出优势。 优化段逻辑倒过来:先按每根柱都挂买卖单算结果,再搜形态。因为 Ref 参考值与 SL 止损是两个优化维,同顺序会重复测相同入场点。任务数在 M5 下约 279039 个 M1 柱 × 55843 个 M5 柱 ≈ 156 亿,所以 tester_opt_prepare() 把 M5 柱分批、限制 M1 索引上限,靠返回的最大分钟柱索引推进下一批,避免暴力枚举。 find_patterns_opt() 跑三维任务空间(柱 / Ref 索引 / SL 索引),用 atomic_add() 累加 int 型统计。因原子函数不支持 double,point 传入 _Point/100,精度损在分位。最终 OptResults[] 读回搜最大净利润组合,转回 double 会略有偏差。 同参数下内建快优显示 Ref=60/SL=350 净利 1000.50,OpenCL 找到 Ref=60/SL=365 净利 1506.40。拿这组进常规测试器复跑结果接近,说明不是精度丢的;切慢速全枚举内建也定位到 SL=365,证明自写优化算法没偏,只是遗传算法跳过了该组合。外汇与贵金属测试请意识到:历史回测近似不等于实盘,杠杆品种高风险。
<span class="keyword">class</span> COpenCLx : <span class="keyword">class="kw">public</span> COpenCL> { <span class="keyword">class="kw">private</span>: COpenCL *ocl; <span class="keyword">class="kw">public</span>: COpenCLx(); ~COpenCLx(); STR_ERROR m_last_error; <span class="comment">class=class="str">"cmt">// 最近的错误结构</span> COCLStat m_stat; <span class="comment">class=class="str">"cmt">// OpenCL 统计 </span> <span class="comment">class=class="str">"cmt">//--- 操作缓冲区</span>
OpenCL 封装类的内存与内核接口
在 MT5 里用 OpenCL 做并行计算,第一步是把显存缓冲和内核调用封装成可复用的 C++ 类方法。下面这组接口定义了缓冲创建、数组读写以及内核参数绑定的最小集合,省去每次手写 OpenCL 样板代码的麻烦。 BufferCreate 负责在设备端开一块字节数为 size_in_bytes 的缓冲,flags 控制读写权限;BufferFromArray 则直接把 MT5 的 MQL5 数组推到指定缓冲偏移,适合把 K 线切片一次性丢给 GPU。BufferRead / BufferWrite 做双向同步,偏移量以元素为单位而非字节,调用时容易算错。 内核侧先用 KernelCreate 按 kernel_name 编译取出执行体,再用 SetArgument 或 SetArgumentBuffer 把标量或缓冲绑到第 arg_index 个参数位。Execute 的 work_dim 与 work_offset[]、work_size[] 决定并行粒度——比如对 1000 根 BAR 做指标计算,work_size 填 {1000} 即可让每个工作项处理一根。 KernelCreate 的实现里有处硬校验:若 ocl 指针为空,立即通过 SET_UERRx 抛 UERR_NO_OCL 并返回 false,避免后续所有内核调用在空对象上崩溃。开 MT5 接自己写的 .cl 文件时,先确认 Init 成功再调这个方法,否则报错会卡在设备初始化阶段而非语法层。
class="type">bool BufferCreate(class="kw">const ENUM_BUFFERS buffer_index,class="kw">const class="type">uint size_in_bytes,class="kw">const class="type">uint flags,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class="kw">template<class="kw">typename T> class="type">bool BufferFromArray(class="kw">const ENUM_BUFFERS buffer_index,T &data[],class="kw">const class="type">uint data_array_offset,class="kw">const class="type">uint data_array_count,class="kw">const class="type">uint flags,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class="kw">template<class="kw">typename T> class="type">bool BufferRead(class="kw">const ENUM_BUFFERS buffer_index,T &data[],class="kw">const class="type">uint cl_buffer_offset,class="kw">const class="type">uint data_array_offset,class="kw">const class="type">uint data_array_count,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class="kw">template<class="kw">typename T> class="type">bool BufferWrite(class="kw">const ENUM_BUFFERS buffer_index,T &data[],class="kw">const class="type">uint cl_buffer_offset,class="kw">const class="type">uint data_array_offset,class="kw">const class="type">uint data_array_count,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class=class="str">"cmt">//--- 设置参数 class="kw">template<class="kw">typename T> class="type">bool SetArgument(class="kw">const ENUM_KERNELS kernel_index,class="kw">const class="type">int arg_index,T value,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class="type">bool SetArgumentBuffer(class="kw">const ENUM_KERNELS kernel_index,class="kw">const class="type">int arg_index,class="kw">const ENUM_BUFFERS buffer_index,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class=class="str">"cmt">//--- 操作内核 class="type">bool KernelCreate(class="kw">const ENUM_KERNELS kernel_index,class="kw">const class="type">class="kw">string kernel_name,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class="type">bool Execute(class="kw">const ENUM_KERNELS kernel_index,class="kw">const class="type">int work_dim,class="kw">const class="type">uint &work_offset[],class="kw">const class="type">uint &work_size[],class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line); class=class="str">"cmt">//--- class="type">bool Init(ENUM_INIT_MODE mode); class="type">void Deinit(class="type">void); }; class="type">bool COpenCLx::KernelCreate(class="kw">const ENUM_KERNELS kernel_index,class="kw">const class="type">class="kw">string kernel_name,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line) { if(ocl==NULL) { SET_UERRx(UERR_NO_OCL,"OpenCL 对象不存在",function,line); class="kw">return false; }
◍ GPU 内核与缓冲区的错误捕获写法
在 MT5 里用 OpenCL 做并行计算,最容易被忽略的是内核创建和缓冲区申请这两步的失败分支。下面这段封装把系统错误码和自定义错误码分开记录,避免你事后在日志里分不清是驱动挂了还是显存不够。
内核创建先调用 ResetLastError 清场,再跑 KernelCreate。若返回 false,先用 SET_ERRx 抓系统级错误;当 GetLastError 为空时,再补一个 UERR_KERNEL_CREATE 的用户错误码。这样双层赋值能保证 m_last_error 永远有内容可查。
缓冲区申请前会先累加 gpu_mem_usage 做预检。注意原文里 m_stat.gpu_mem_usage+=size_in_bytes)==false 这种写法实际依赖重载的布尔转换,若你抄代码要确认 CMemsize 类确实实现了 Comp 比较,否则显存超限不会被拦下。
四个宏 SET_ERR / SET_ERRx / SET_UERR / SET_UERRx 的区别只在是否用 __FUNCTION__、__LINE__ 自动填位,以及错误基数是 ERR_USER_ERROR_FIRST 偏移。做 EA 调试时,建议直接把 m_last_error.comment 打印到 Experts 日志,能省掉一半排查时间。
外汇与贵金属行情受杠杆和跳空影响,GPU 加速回测仅降低计算耗时,不改善策略胜率,实盘前务必在模拟盘验证。
class=class="str">"cmt">//--- 运行内核执行 ::ResetLastError(); if(!ocl.KernelCreate(kernel_index,kernel_name)) { class="type">class="kw">string comment="Failed to create kernel "+EnumToString(kernel_index)+", name \""+kernel_name+"\""; SET_ERRx(comment,function,line); if(!m_last_error.code) SET_UERRx(UERR_KERNEL_CREATE,comment,function,line); class="kw">return(false); } class=class="str">"cmt">//--- class="kw">return true; } class="kw">struct STR_ERROR { class="type">int code; class=class="str">"cmt">// 代码 class="type">class="kw">string comment; class=class="str">"cmt">// 注释 class="type">class="kw">string function; class=class="str">"cmt">// 出现错误的函数 class="type">int line; class=class="str">"cmt">// 出现错误的字符串信息 }; class="macro">#define SET_ERR(c) do {m_last_error.function = __FUNCTION__; \ m_last_error.line =__LINE__; \ m_last_error.code=::GetLastError(); m_last_error.comment=c;} class="kw">while(class="num">0) class="macro">#define SET_ERRx(c,f,l) do {m_last_error.function = f; m_last_error.line = l; \ m_last_error.code=::GetLastError(); m_last_error.comment=c;} class="kw">while(class="num">0) class="macro">#define SET_UERR(err,c) do {m_last_error.function = __FUNCTION__; \ m_last_error.line =__LINE__; \ m_last_error.code=ERR_USER_ERROR_FIRST+err; m_last_error.comment=c;} class="kw">while(class="num">0) class="macro">#define SET_UERRx(err,c,f,l) do {m_last_error.function = f; m_last_error.line = l; \ m_last_error.code=ERR_USER_ERROR_FIRST+err; m_last_error.comment=c;} class="kw">while(class="num">0) class="type">bool COpenCLx::BufferCreate(class="kw">const ENUM_BUFFERS buffer_index,class="kw">const class="type">uint size_in_bytes,class="kw">const class="type">uint flags,class="kw">const class="type">class="kw">string function,class="kw">const class="type">int line) { if(ocl==NULL) { SET_UERRx(UERR_NO_OCL,"OpenCL 对象不存在",function,line); class="kw">return false; } class=class="str">"cmt">//--- 探索和检查空闲内存 if((m_stat.gpu_mem_usage+=size_in_bytes)==false) { CMemsize cmem=m_stat.gpu_mem_usage.Comp(size_in_bytes); SET_UERRx(UERR_NO_ENOUGH_MEM,"没有空闲 GPU 内存,不够 "+cmem.ToStr(),function,line); class="kw">return false; } class=class="str">"cmt">//--- 创建缓冲区 ::ResetLastError(); if(ocl.BufferCreate(buffer_index,size_in_bytes,flags)==false) { class="type">class="kw">string comment="创建缓冲区失败 "+EnumToString(buffer_index); SET_ERRx(comment,function,line); if(!m_last_error.code) SET_UERRx(UERR_BUFFER_CREATE,comment,function,line); class="kw">return(false); } class=class="str">"cmt">//--- class="kw">return(true); }
「OpenCL 初始化时的缓冲区与内核装配」
在 MT5 里用 OpenCL 做形态识别,第一步是把显存缓冲区和计算内核先搭起来。上面这段先用 BufferCreate 给 buf_ORDER_M1 开了 len*sizeof(int) 字节的可读写缓冲,若返回 false 直接退出,避免后面空跑。 为了方便反复建缓冲,代码用宏 _BufferCreate 把「建不成就 return false」包了一层,再顺手用 _KernelCreate 挂上名为 find_patterns 的内核。这种宏封装能让 Init 里的错误分支少写很多重复判断。 COpenCLx::Init 里有个 while(!IsStopped()) 的壳,实际只跑一轮:先 new 出 COpenCL 对象,再 Initialize 连设备;若设备不支持 double(cl_khr_fp64),会写 UERR_DOUBLE_NOT_SUPP 并 break——老核显常踩这坑,跑前先在终端看支持列表。 根据 mode 不同,挂的内核集不一样:i_MODE_TESTER 挂 find_patterns / array_fill / order_to_M1 / tester_step 四个;i_MODE_OPTIMIZER 则换挂 opt 系列内核。SetBuffersCount 和 GetGlobalMemorySize 拿到的显存上限会写进 m_stat,方便后面估自己吃了多少卡。 外汇与贵金属杠杆高、GPU 并行回测也只是概率参考,显存申请失败不代表策略失效,但真机验证前最好先确认显卡双精度支持,否则初始化阶段就会断。
if(BufferCreate(buf_ORDER_M1,len*class="kw">sizeof(class="type">int),CL_MEM_READ_WRITE,__FUNCTION__,__LINE__)==false) class="kw">return false; class="macro">#define _BufferCreate(buffer_index,size_in_bytes,flags) \ if(BufferCreate(buffer_index,size_in_bytes,flags,__FUNCTION__,__LINE__)==false) class="kw">return false _BufferCreate(buf_ORDER_M1,len*class="kw">sizeof(class="type">int),CL_MEM_READ_WRITE); _KernelCreate(k_FIND_PATTERNS,"find_patterns"); class="type">bool COpenCLx::Init(ENUM_INIT_MODE mode) { if(ocl) Deinit(); class=class="str">"cmt">//--- 创建 COpenCL 类的对象 ocl=new COpenCL; class="kw">while(!IsStopped()) { class=class="str">"cmt">//--- 初始化 OpenCL ::ResetLastError(); if(!ocl.Initialize(cl_tester,true)) { SET_ERR("OpenCL 初始化错误"); class="kw">break; } class=class="str">"cmt">//--- 检查是否支持操作 &class="macro">#x27;class="type">class="kw">double&class="macro">#x27; 类型 if(!ocl.SupportDouble()) { SET_UERR(UERR_DOUBLE_NOT_SUPP,"这个设备不支持操作 class="type">class="kw">double (cl_khr_fp64) "); class="kw">break; } class=class="str">"cmt">//--- 设置内核数量 if(!ocl.SetKernelsCount(OCL_KERNELS_COUNT)) class="kw">break; class=class="str">"cmt">//--- 创建内核 if(mode==i_MODE_TESTER) { _KernelCreate(k_FIND_PATTERNS,"find_patterns"); _KernelCreate(k_ARRAY_FILL,"array_fill"); _KernelCreate(k_ORDER_TO_M1,"order_to_M1"); _KernelCreate(k_TESTER_STEP,"tester_step"); }else if(mode==i_MODE_OPTIMIZER){ _KernelCreate(k_ARRAY_FILL,"array_fill"); _KernelCreate(k_TESTER_OPT_PREPARE,"tester_opt_prepare"); _KernelCreate(k_TESTER_OPT_STEP,"tester_opt_step"); _KernelCreate(k_FIND_PATTERNS_OPT,"find_patterns_opt"); } else class="kw">break; class=class="str">"cmt">//--- 创建缓冲区 if(!ocl.SetBuffersCount(OCL_BUFFERS_COUNT)) { SET_UERR(UERR_SET_BUF_COUNT,"创建缓冲区失败"); class="kw">break; } class=class="str">"cmt">//--- 取得 RAM 大小 class="type">long gpu_mem_size; if(ocl.GetGlobalMemorySize(gpu_mem_size)==false) { SET_UERR(UERR_GET_MEMORY_SIZE,"取得 RAM 值失败"); class="kw">break; } m_stat.gpu_mem_size.Set(gpu_mem_size); m_stat.gpu_mem_usage.Max(gpu_mem_size); class="kw">return true; } Deinit(); class="kw">return false; } class="type">void COpenCLx::Deinit() { if(ocl!=NULL)
把行情塞进显存前的类结构
做 GPU 加速回测,第一步不是写核函数,而是把 MT5 的 OHLC 和时间序列规整成一个能整体抛给 OpenCL 的容器。下面这段 CBuffering 类就是干这个的:它用 Open/High/Low/Close/Spread/Time 六个 double 与 datetime 数组承接 Copy() 拉来的原始数据,Depth 记录实际缓冲深度。 私有成员里几个量直接决定上传行为:m_timeout 是单次上传允许的毫秒数,m_ts_abort 是整段运行应当被打断的时间戳,m_memory_usage 在每次拷贝后累加显存占用量。你在 MT5 里跑大周期多品种扫描时,这几个值能帮你判断某次上传是不是已经拖慢了主线程。 注意 ForceUploading(datetime from, datetime to) 是强制按时间窗重传,普通 Copy() 则靠 symbol+period+from+to 拉取,多传一个 point 参数(默认 0)用来做价格精度归一。SpreadBufEnable() 有读写两个重载,默认关,开之后才会填 Spread[]——外汇和贵金属点差跳变频繁,开着能更真,但显存占用会多一成左右,属于典型的风险与精度权衡。 调用侧 LoadTimeseriesOCL() 展示了实际抛数据:_BufferFromArray() 把 m_sbuf.Open 从 0 到 Depth 整段标记为 CL_MEM_READ_ONLY 建缓冲,High/Low 同理。这里只读标志不能省,否则 GPU 端写回会触发非法内存,MT5 终端会直接崩 EA。
{
class=class="str">"cmt">//--- 删除 OpenCL 对象
ocl.Shutdown();
class="kw">delete ocl;
ocl=NULL;
}
}
class CBuffering
{
class="kw">private:
class="type">class="kw">string m_symbol;
ENUM_TIMEFRAMES m_period;
class="type">int m_maxbars;
class="type">uint m_memory_usage; class=class="str">"cmt">//使用内存的量
class="type">bool m_spread_ena; class=class="str">"cmt">//上传点差缓冲区
class="type">class="kw">datetime m_from;
class="type">class="kw">datetime m_to;
class="type">uint m_timeout; class=class="str">"cmt">//上传超时的毫秒数
class="type">class="kw">ulong m_ts_abort; class=class="str">"cmt">//当运行应当被打断的时间毫秒数
class=class="str">"cmt">//--- 强制上传
class="type">bool ForceUploading(class="type">class="kw">datetime from,class="type">class="kw">datetime to);
class="kw">public:
CBuffering();
~CBuffering();
class=class="str">"cmt">//--- 缓冲区中的数据量
class="type">int Depth;
class=class="str">"cmt">//--- 缓冲区
class="type">class="kw">double Open[];
class="type">class="kw">double High[];
class="type">class="kw">double Low[];
class="type">class="kw">double Close[];
class="type">class="kw">double Spread[];
class="type">class="kw">datetime Time[];
class=class="str">"cmt">//--- 取得上传数据的实时边界
class="type">class="kw">datetime TimeFrom(class="type">void){class="kw">return m_from;}
class="type">class="kw">datetime TimeTo(class="type">void){class="kw">return m_to;}
class=class="str">"cmt">//---
class="type">int Copy(class="type">class="kw">string symbol,ENUM_TIMEFRAMES period,class="type">class="kw">datetime from,class="type">class="kw">datetime to,class="type">class="kw">double point=class="num">0);
class="type">uint GetMemoryUsage(class="type">void){class="kw">return m_memory_usage;}
class="type">bool SpreadBufEnable(class="type">void){class="kw">return m_spread_ena;}
class="type">void SpreadBufEnable(class="type">bool ena){m_spread_ena=ena;}
class="type">void SetTimeout(class="type">uint timeout){m_timeout=timeout;}
};
class="type">bool CTestPatterns::LoadTimeseriesOCL()
{
class=class="str">"cmt">//--- 开盘价缓冲区:
_BufferFromArray(buf_OPEN,m_sbuf.Open,class="num">0,m_sbuf.Depth,CL_MEM_READ_ONLY);
class=class="str">"cmt">//--- 最高价缓冲区:
_BufferFromArray(buf_HIGH,m_sbuf.High,class="num">0,m_sbuf.Depth,CL_MEM_READ_ONLY);
class=class="str">"cmt">//--- 最低价缓冲区:◍ 把多周期数据塞进 OpenCL 再判定针杆
GPU 并行扫描价格行为,第一步是把宿主端的数组搬到显存。下面这组调用把当前周期与 M1 周期的 OHLC、时间、点差全部映射为只读缓冲区:低、收、时间各一条,M1 侧再补 Open/High/Low/Close/Spread/Time 七条,Depth 决定搬运的 K 线根数,CL_MEM_READ_ONLY 告诉驱动无需回写。 _BufferFromArray(buf_LOW,m_sbuf.Low,0,m_sbuf.Depth,CL_MEM_READ_ONLY); //--- 收盘价缓冲区: _BufferFromArray(buf_CLOSE,m_sbuf.Close,0,m_sbuf.Depth,CL_MEM_READ_ONLY); //--- 时间缓冲区: _BufferFromArray(buf_TIME,m_sbuf.Time,0,m_sbuf.Depth,CL_MEM_READ_ONLY); //--- Open (M1) 缓冲区: _BufferFromArray(buf_OPEN_M1,m_tbuf.Open,0,m_tbuf.Depth,CL_MEM_READ_ONLY); //--- High (M1) 缓冲区: _BufferFromArray(buf_HIGH_M1,m_tbuf.High,0,m_tbuf.Depth,CL_MEM_READ_ONLY); //--- Low (M1) 缓冲区: _BufferFromArray(buf_LOW_M1,m_tbuf.Low,0,m_tbuf.Depth,CL_MEM_READ_ONLY); //--- Close (M1) 缓冲区: _BufferFromArray(buf_CLOSE_M1,m_tbuf.Close,0,m_tbuf.Depth,CL_MEM_READ_ONLY); //--- Spread (M1) 缓冲区: _BufferFromArray(buf_SPREAD_M1,m_tbuf.Spread,0,m_tbuf.Depth,CL_MEM_READ_ONLY); //--- Time (M1) 缓冲区: _BufferFromArray(buf_TIME_M1,m_tbuf.Time,0,m_tbuf.Depth,CL_MEM_READ_ONLY); //--- 复制成功 return true; } 形态用位掩码定义,PAT_PINBAR_BEARISH 是 1<<0,PAT_ENGULFING_BULLISH 是 1<<3,传入 flags 做按位与就能只算你关心的那几种。O(i) H(i) L(i) C(i) 四个宏把数组下标访问压成单字母,内核里写起来接近伪代码。 #define PAT_NONE 0 #define PAT_PINBAR_BEARISH (1<<0) #define PAT_PINBAR_BULLISH (1<<1) #define PAT_ENGULFING_BEARISH (1<<2) #define PAT_ENGULFING_BULLISH (1<<3) #define O(i) Open[i] #define H(i) High[i] #define L(i) Low[i] #define C(i) Close[i] 看跌针杆的判断集中在第 1 根:上影线 tail = H(1)-max(O(1),C(1)),要求 tail>=ref,且第 1 根实体绝对值小于 tail,第 0 根收阳、第 2 根收阴、第 1 根高点超越相邻两根。ref 一般取 ATR 的倍数,EURUSD 在 M5 上常用 0.3~0.5 倍 ATR 做阈值,外汇和贵金属杠杆高,参数过紧会漏信号、过宽会刷屏,上机前先跑历史窗口数一下命中率。 uint Check(__global double *Open,__global double *High,__global double *Low,__global double *Close,double ref,uint flags) { //--- 看跌针杆 if((flags&PAT_PINBAR_BEARISH)!=0) {// double tail=H(1)-fmax(O(1),C(1)); if(tail>=ref && C(0)>O(0) && O(2)>C(2) && H(1)>fmax(H(0),H(2)) && fabs(O(1)-C(1))<tail) return PAT_PINBAR_BEARISH; } //--- 看涨针杆 if((flags&PAT_PINBAR_BULLISH)!=0) { double tail=fmin(O(1),C(1))-L(1);
_BufferFromArray(buf_LOW,m_sbuf.Low,class="num">0,m_sbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- 收盘价缓冲区: _BufferFromArray(buf_CLOSE,m_sbuf.Close,class="num">0,m_sbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- 时间缓冲区: _BufferFromArray(buf_TIME,m_sbuf.Time,class="num">0,m_sbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- Open(M1) 缓冲区: _BufferFromArray(buf_OPEN_M1,m_tbuf.Open,class="num">0,m_tbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- High(M1) 缓冲区: _BufferFromArray(buf_HIGH_M1,m_tbuf.High,class="num">0,m_tbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- Low(M1) 缓冲区: _BufferFromArray(buf_LOW_M1,m_tbuf.Low,class="num">0,m_tbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- Close(M1) 缓冲区: _BufferFromArray(buf_CLOSE_M1,m_tbuf.Close,class="num">0,m_tbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- Spread(M1) 缓冲区: _BufferFromArray(buf_SPREAD_M1,m_tbuf.Spread,class="num">0,m_tbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- Time(M1) 缓冲区: _BufferFromArray(buf_TIME_M1,m_tbuf.Time,class="num">0,m_tbuf.Depth,CL_MEM_READ_ONLY); class=class="str">"cmt">//--- 复制成功 class="kw">return true; } class=class="str">"cmt">//--- 形态 class="macro">#define PAT_NONE class="num">0 class="macro">#define PAT_PINBAR_BEARISH(class="num">1<<class="num">0) class="macro">#define PAT_PINBAR_BULLISH(class="num">1<<class="num">1) class="macro">#define PAT_ENGULFING_BEARISH(class="num">1<<class="num">2) class="macro">#define PAT_ENGULFING_BULLISH(class="num">1<<class="num">3) class=class="str">"cmt">//--- 价格 class="macro">#define O(i) Open[i] class="macro">#define H(i) High[i] class="macro">#define L(i) Low[i] class="macro">#define C(i) Close[i] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查形态是否存在 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">uint Check(__global class="type">class="kw">double *Open,__global class="type">class="kw">double *High,__global class="type">class="kw">double *Low,__global class="type">class="kw">double *Close,class="type">class="kw">double ref,class="type">uint flags) { class=class="str">"cmt">//--- 看跌针杆 if((flags&PAT_PINBAR_BEARISH)!=class="num">0) {class=class="str">"cmt">// class="type">class="kw">double tail=H(class="num">1)-fmax(O(class="num">1),C(class="num">1)); if(tail>=ref && C(class="num">0)>O(class="num">0) && O(class="num">2)>C(class="num">2) && H(class="num">1)>fmax(H(class="num">0),H(class="num">2)) && fabs(O(class="num">1)-C(class="num">1))<tail) class="kw">return PAT_PINBAR_BEARISH; } class=class="str">"cmt">//--- 看涨针杆 if((flags&PAT_PINBAR_BULLISH)!=class="num">0) {class=class="str">"cmt">// class="type">class="kw">double tail=fmin(O(class="num">1),C(class="num">1))-L(class="num">1);