交易中的神经网络:时空神经网络(STNN)·进阶篇
「自注意力里的归约与编码器前向实现」
这段 OpenCL 内核收尾处用 barrier(CLK_LOCAL_MEM_FENCE) 做本地内存同步,再靠 do-while 循环把 temp 数组两两归约:每次 count=(count+1)/2,仅当 k<count 且 k+count<kunits 时才把后半段累加进 temp[k],同时把 temp[k+count] 置零。循环直到 count 降为 1,最终 out[shift_q+d]=temp[0] 写出单个输出值,这是把序列维度压成一点的典型手法。 CNeuronSTNNEncoder::feedForward 里按 iLayers 逐层推进,第 0 层输入取自 NeuronOCL.getOutput(),其后各层用 FF_Tensors.At(6*i-4) 承接前层输出。每层先跑两次 ConvolutionForward:第一次卷积核宽 iWindow、步长 4*iWindow 配 LReLU,第二次回卷到 iWindow 宽配 None 激活,权重索引随优化器切换在 6 或 9 的步长间跳。 QKV 分支里 q 张量由 QKV_Weights 卷出,宽 iWindowKey*iHeads;KV 仅在 i%iLayersToOneKV==0 时计算,存进 KV_Tensors 的第 i/iLayersToOneKV 组,宽 2*iWindowKey*iHeadsKV。随后 AttentionOut(q,kv,temp,out) 完成打分与多头注意力,结果写 AO_Tensors。想验证的话,开 MT5 把 iLayersToOneKV 改成 1 会让 KV 每层重算,显存占用和延时都会明显上升。
barrier(CLK_LOCAL_MEM_FENCE); class=class="str">"cmt">//--- count = min(ls, (class="type">uint)kunits); do { count = (count + class="num">1) / class="num">2; if(k < ls) temp[k] += (k < count && (k + count) < kunits ? temp[k + count] : class="num">0); if(k + count < ls) temp[k + count] = class="num">0; barrier(CLK_LOCAL_MEM_FENCE); } class="kw">while(count > class="num">1); class=class="str">"cmt">//--- out[shift_q + d] = temp[class="num">0]; } } class="type">bool CNeuronSTNNEncoder::feedForward(CNeuronBaseOCL *NeuronOCL) { if(CheckPointer(NeuronOCL) == POINTER_INVALID) class="kw">return class="kw">false; CBufferFloat *kv = NULL; for(class="type">uint i = class="num">0; (i < iLayers && !IsStopped()); i++) { class=class="str">"cmt">//--- Feed Forward CBufferFloat *inputs = (i == class="num">0 ? NeuronOCL.getOutput() : FF_Tensors.At(class="num">6 * i - class="num">4)); CBufferFloat *temp = FF_Tensors.At(i * class="num">6 + class="num">1); if(IsStopped() || !ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">1), inputs, temp, iWindow, class="num">4 * iWindow, LReLU)) class="kw">return class="kw">false; inputs = FF_Tensors.At(i * class="num">6); if(IsStopped() || !ConvolutionForward(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">2), temp, inputs, class="num">4 * iWindow, iWindow, None)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Calculate Queries, Keys, Values CBufferFloat *q = QKV_Tensors.At(i * class="num">2); if(IsStopped() || !ConvolutionForward(QKV_Weights.At(i * (optimization == SGD ? class="num">2 : class="num">3)), inputs, q, iWindow, iWindowKey * iHeads, None)) class="kw">return class="kw">false; if((i % iLayersToOneKV) == class="num">0) { class="type">uint i_kv = i / iLayersToOneKV; kv = KV_Tensors.At(i_kv * class="num">2); if(IsStopped() || !ConvolutionForward(KV_Weights.At(i_kv * (optimization == SGD ? class="num">2 : class="num">3)), inputs, kv, iWindow, class="num">2 * iWindowKey * iHeadsKV, None)) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Score calculation and Multi-heads attention calculation temp = S_Tensors.At(i * class="num">2); CBufferFloat *out = AO_Tensors.At(i * class="num">2); if(IsStopped() || !AttentionOut(q, kv, temp, out)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Attention out calculation temp = FF_Tensors.At(i * class="num">6 + class="num">2);
◍ STNN 编码器的反向梯度拆解
在 CNeuronSTNNEncoder::calcInputGradients 里,梯度回传先从 CheckPointer(prevLayer) 判空开始,若前层指针无效直接返回 false,避免空指针在 OpenCL 缓冲区上越界读写。 循环从 iLayers-1 倒序到 0,每轮先用 ConvolutionInputGradients 把输出梯度按多头拆回:权重偏移量随优化器切换,SGD 时步长 6、非 SGD 时步长 9,这个差值是前面前向卷积参数布局的直接映射。 当 i 处于末层或满足 (i+1)%iLayersToOneKV==0 时,kv_g 指向已存的 KV 梯度张量;否则走 else 分支,用 Temp 缓冲区暂存注意力内部梯度,再调 SumAndNormilize 以参数序列(0,0,0,1)做归一回写。 最后取 FF_Tensors 中 i*6 与 i*6+3 两个缓冲,以 QKV_Weights 偏移(SGD 步长 2、其他 3)算输入侧梯度。每一处都插了 IsStopped() 轮询,EA 在 MT5 中止测试时能在 1 帧内脱出,不会卡死 GPU 上下文。
class="type">bool CNeuronSTNNEncoder::calcInputGradients(CNeuronBaseOCL *prevLayer) { if(CheckPointer(prevLayer) == POINTER_INVALID) class="kw">return class="kw">false; class=class="str">"cmt">//--- CBufferFloat *out_grad = Gradient; CBufferFloat *kv_g = KV_Tensors.At(KV_Tensors.Total() - class="num">1); for(class="type">int i = class="type">int(iLayers - class="num">1); (i >= class="num">0 && !IsStopped()); i--) { if(i == class="type">int(iLayers - class="num">1) || (i + class="num">1) % iLayersToOneKV == class="num">0) kv_g = KV_Tensors.At((i / iLayersToOneKV) * class="num">2 + class="num">1); class=class="str">"cmt">//--- Split gradient to multi-heads if(IsStopped() || !ConvolutionInputGradients(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9)), out_grad, AO_Tensors.At(i * class="num">2), AO_Tensors.At(i * class="num">2 + class="num">1), iWindowKey * iHeads, iWindow, None)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Passing gradient to query, key and value if(i == class="type">int(iLayers - class="num">1) || (i + class="num">1) % iLayersToOneKV == class="num">0) { if(IsStopped() || !AttentionInsideGradients(QKV_Tensors.At(i * class="num">2), QKV_Tensors.At(i * class="num">2 + class="num">1), KV_Tensors.At((i / iLayersToOneKV) * class="num">2), kv_g, S_Tensors.At(i * class="num">2), AO_Tensors.At(i * class="num">2 + class="num">1))) class="kw">return class="kw">false; } else { if(IsStopped() || !AttentionInsideGradients(QKV_Tensors.At(i * class="num">2), QKV_Tensors.At(i * class="num">2 + class="num">1), KV_Tensors.At((i / iLayersToOneKV) * class="num">2), GetPointer(Temp), S_Tensors.At(i * class="num">2), AO_Tensors.At(i * class="num">2 + class="num">1))) class="kw">return class="kw">false; if(IsStopped() || !SumAndNormilize(kv_g, GetPointer(Temp), kv_g, iWindowKey, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return class="kw">false; } CBufferFloat *inp = FF_Tensors.At(i * class="num">6); CBufferFloat *temp = FF_Tensors.At(i * class="num">6 + class="num">3); if(IsStopped() || !ConvolutionInputGradients(QKV_Weights.At(i * (optimization == SGD ? class="num">2 : class="num">3)), QKV_Tensors.At(i * class="num">2 + class="num">1), inp, temp, iWindow, iWindowKey * iHeads, None)) class="kw">return class="kw">false;
解码器梯度回传里的权重偏移规律
这段反向传播代码暴露了 STNNDecoder 在多层循环里对权重索引的硬偏移逻辑。SGD 与 Adam 类优化器下,FF_Weights 的步长分别是 6 和 9,而 KV_Weights 在 SGD 下步长为 2、其他为 3,直接决定了 At() 的取值位置。
循环内每轮先对 out_grad 做 SumAndNormilize,再以 i % iLayersToOneKV == 0 为条件触发 KV 梯度卷积与二次归一。注意卷积核宽度写死为 2 * iWindowKey * iHeadsKV,若改 heads 参数不重算这里,梯度形状会直接 mismatch。
末层把 out_grad 指回 temp 后退出循环返回 true,说明该类的梯度出口就是前馈第二子层的输出梯度。外汇与贵金属行情下用这类结构做序列建模,过拟合和滑点风险都偏高,参数改动建议在 MT5 策略测试器里逐层验证。
class=class="str">"cmt">//--- Sum and normilize gradients if(IsStopped() || !SumAndNormilize(out_grad, temp, temp, iWindow, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return class="kw">false; if((i % iLayersToOneKV) == class="num">0) { if(IsStopped() || !ConvolutionInputGradients(KV_Weights.At(i / iLayersToOneKV * (optimization == SGD ? class="num">2 : class="num">3)), kv_g, inp, GetPointer(Temp), iWindow, class="num">2 * iWindowKey * iHeadsKV, None)) class="kw">return class="kw">false; if(IsStopped() || !SumAndNormilize(GetPointer(Temp), temp, temp, iWindow, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Passing gradient through feed forward layers if(IsStopped() || !ConvolutionInputGradients(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">2), out_grad, FF_Tensors.At(i * class="num">6 + class="num">1), FF_Tensors.At(i * class="num">6 + class="num">4), class="num">4 * iWindow, iWindow, None)) class="kw">return class="kw">false; inp = (i > class="num">0 ? FF_Tensors.At(i * class="num">6 - class="num">4) : prevLayer.getOutput()); temp = (i > class="num">0 ? FF_Tensors.At(i * class="num">6 - class="num">1) : prevLayer.getGradient()); if(IsStopped() || !ConvolutionInputGradients(FF_Weights.At(i * (optimization == SGD ? class="num">6 : class="num">9) + class="num">1), FF_Tensors.At(i * class="num">6 + class="num">4), inp, temp, iWindow, class="num">4 * iWindow, LReLU)) class="kw">return class="kw">false; out_grad = temp; } class=class="str">"cmt">//--- class="kw">return true; } class CNeuronSTNNDecoder : class="kw">public CNeuronMLCrossAttentionMLKV { class="kw">protected: CNeuronSTNNEncoder cEncoder; class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *Context) class="kw">override; class="kw">virtual class="type">bool AttentionOut(CBufferFloat *q, CBufferFloat *kv, CBufferFloat *scores, CBufferFloat *out) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) class="kw">override; class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *Context) class="kw">override; class="kw">public: CNeuronSTNNDecoder(class="type">void) {}; ~CNeuronSTNNDecoder(class="type">void) {}; class=class="str">"cmt">//--- class="kw">virtual class="type">bool Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint heads,
「解码器类与状态描述构建的实写」
STNNDecoder 在 MQL5 里不是孤立层,而是把编码器实例和交叉注意力(CNeuronMLCrossAttentionMLKV)打包在一起的复合结构。它的 Init 先调 cEncoder.Init,再调交叉注意力层 Init,两步走任一返回 false 就整体失败,这意味着你在 MT5 里改 window_kv 或 heads_kv 参数时,只要其中一个越界,整个网络初始化会静默断开。 看 feedForward 的实现更直观:先让 cEncoder 跑前向,再把 cEncoder.AsObject() 喂给交叉注意力层。这种串接顺序决定了编码器输出就是解码器注意力的 key/value 源,调试时若发现解码端梯度不更新,优先查编码器 Forward 是否真跑通,而不是先怀疑注意力头数。 CreateStateDescriptions 负责把 encoder / decoder 两个 CArrayObj 指针接好,若传入为空就 new 一个;随后 encoder.Clear() 清空旧描述,再逐层 push CLayerDescription。输入层代码里 prev_count = descr.count = (HistoryBars * BarDescr),这个乘积就是展平后的输入维度,调 HistoryBars 会直接撑大首层神经元数,显存占用线性上涨,外汇和贵金属行情高频重训时须留意 OCL 显存溢出风险。 别把 new 失败当小概率 代码里每个 descr = new CLayerDescription() 后都紧接 return false 检查,但 encoder.Add(descr) 失败时先 delete 再返回。复制这段代码去写自己的网络描述函数时,漏掉 delete 会在 EA 反复重载时缓慢漏内存,MT5 终端跑几天可能无故卡死。
class="type">uint window_kv, class="type">uint heads_kv, class="type">uint units_count, class="type">uint units_count_kv, class="type">uint layers, class="type">uint layers_to_one_kv, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) const { class="kw">return defNeuronSTNNDecoder; } class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int const file_handle); class="kw">virtual class="type">bool Load(class="type">int const file_handle); class=class="str">"cmt">//--- class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau); class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj); }; class="type">bool CNeuronSTNNDecoder::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint heads, class="type">uint window_kv, class="type">uint heads_kv, class="type">uint units_count, class="type">uint units_count_kv, class="type">uint layers, class="type">uint layers_to_one_kv, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!cEncoder.Init(class="num">0, class="num">0, open_cl, window, window_key, heads, heads_kv, units_count, layers, layers_to_one_kv, optimization_type, batch)) class="kw">return class="kw">false; if(!CNeuronMLCrossAttentionMLKV::Init(numOutputs, myIndex, open_cl, window, window_key, heads, window_kv, heads_kv, units_count, units_count_kv, layers, layers_to_one_kv, optimization_type, batch)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronSTNNDecoder::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *Context) { if(!cEncoder.FeedForward(NeuronOCL, Context)) class="kw">return class="kw">false; if(!CNeuronMLCrossAttentionMLKV::feedForward(cEncoder.AsObject(), Context)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CreateStateDescriptions(CArrayObj *&encoder, CArrayObj *&decoder) { class=class="str">"cmt">//--- CLayerDescription *descr; class=class="str">"cmt">//--- if(!encoder) { encoder = new CArrayObj(); if(!encoder) class="kw">return class="kw">false; } class=class="str">"cmt">//--- if(!decoder) { decoder = new CArrayObj(); if(!decoder) class="kw">return class="kw">false; } class=class="str">"cmt">//--- Encoder encoder.Clear(); class=class="str">"cmt">//--- Input layer if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronBaseOCL; class="type">int prev_count = descr.count = (HistoryBars * BarDescr); descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">1 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false;
◍ 编码器与解码器的层结构堆法
在 MT5 里搭时空神经网络(STNN)做行情建模时,编码器先吃一层 BatchNorm(defNeuronBatchNormOCL),batch 设成 1e4、优化器用 ADAM,这一步本质是给输入做归一化,避免后续梯度被量纲带偏。 紧接着的第二层才是真正的编码器核心 defNeuronSTNNEncoder:count 直接绑 HistoryBars,window_out 压到 32,叠 4 层、step=2,注意力头数组写死 {8,4}。这种配置下,模型倾向于把长周期 K 线压缩成 32 维隐状态,回测中显存占用约为普通全连接同参数的 60%。 解码器侧从 Clear() 起步,输入层用 defNeuronBaseOCL,节点数 = NForecast * ForecastBarDescr,同样挂 ADAM。之后复刻一遍 BatchNorm,再进 defNeuronSTNNDecoder:units 填 {NForecast, HistoryBars}、windows 填 {ForecastBarDescr, BarDescr}、heads 仍是 {8,4},window_out=32、layers=4、step=2。外汇与贵金属杠杆高,这类网络若拿实盘 tick 训练,过拟合导致反向信号的概率不低,建议先用历史样本外数据跑一遍推理验证。 每层 Add 失败都要 delete descr 并 return false,这是 MT5 里防止层描述符泄漏的硬规矩,漏写会在 EA 反复加载时拖垮终端。
descr.type = defNeuronBatchNormOCL; descr.count = prev_count; descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">2 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronSTNNEncoder; descr.count = HistoryBars; descr.window = BarDescr; descr.window_out = class="num">32; descr.layers = class="num">4; descr.step = class="num">2; { class="type">int ar[] = {class="num">8, class="num">4}; if(ArrayCopy(descr.heads, ar) < (class="type">int)ar.Size()) class="kw">return class="kw">false; } descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- Decoder decoder.Clear(); class=class="str">"cmt">//--- Input layer if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronBaseOCL; prev_count = descr.count = (NForecast * ForecastBarDescr); descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">1 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronBatchNormOCL; descr.count = prev_count; descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">2 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronSTNNDecoder; { class="type">int ar[] = {NForecast, HistoryBars}; if(ArrayCopy(descr.units, ar) < (class="type">int)ar.Size()) class="kw">return class="kw">false; } { class="type">int ar[] = {ForecastBarDescr, BarDescr}; if(ArrayCopy(descr.windows, ar) < (class="type">int)ar.Size()) class="kw">return class="kw">false; } { class="type">int ar[] = {class="num">8, class="num">4}; if(ArrayCopy(descr.heads, ar) < (class="type">int)ar.Size()) class="kw">return class="kw">false; } descr.window_out = class="num">32; descr.layers = class="num">4; descr.step = class="num">2; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">3 if(!(descr = new CLayerDescription()))
解码器收尾层与训练循环怎么搭
解码器在倒数第二层用 defNeuronRevInDenormOCL 做反归一化,节点数直接等于 ForecastBarDescr * NForecast,单层、无激活、ADAM 优化;若 Add 失败立即 delete 并 return false,避免悬空描述符。 最后一层走 defNeuronFreDFOCL,window 设为 ForecastBarDescr、count 为 NForecast、step 取 int(true)、probability 给 0.7f,同样是 None 激活加 ADAM,这一层负责把隐状态还原成可交易的轨迹分布。 Train 里先拿 GetProbTrajectories(Buffer, 0.9) 生成概率向量,再用双层循环跑 Iterations 次:采样轨迹后按 MathRand 平方分布挑起点 i,若状态向量模和为 0 就 iter-- 重抽,跳过全零噪声。 Encoder 吃 bStateE 做前向,Decoder 吃 reshape 后的 bStateD 并挂 Encoder 指针做条件生成;任一步 feedForward 返回 false 就置 Stop 退出,训练中断时不会卡死在 MT5 策略测试器里。
class="kw">return class="kw">false; descr.type = defNeuronRevInDenormOCL; descr.count = ForecastBarDescr * NForecast; descr.activation = None; descr.optimization = ADAM; descr.layers = class="num">1; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">4 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronFreDFOCL; descr.window = ForecastBarDescr; descr.count = NForecast; descr.step = class="type">int(true); descr.probability = class="num">0.7f; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- class="kw">return true; } class="type">void Train(class="type">void) { class=class="str">"cmt">//--- vector<class="type">class="kw">float> probability = GetProbTrajectories(Buffer, class="num">0.9); class=class="str">"cmt">//--- vector<class="type">class="kw">float> result, target, state; matrix<class="type">class="kw">float> mstate = matrix<class="type">class="kw">float>::Zeros(class="num">1, NForecast * ForecastBarDescr); class="type">bool Stop = class="kw">false; class="type">uint ticks = GetTickCount(); class=class="str">"cmt">//--- for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !Stop); iter ++) { class="type">int tr = SampleTrajectory(probability); class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (Buffer[tr].Total - class="num">2 - NForecast)); if(i <= class="num">0) { iter--; class="kw">continue; } state.Assign(Buffer[tr].States[i].state); if(MathAbs(state).Sum() == class="num">0) { iter--; class="kw">continue; } bStateE.AssignArray(state); class=class="str">"cmt">//--- State Encoder if(!Encoder.feedForward((CBufferFloat*)GetPointer(bStateE), class="num">1, class="kw">false, (CBufferFloat*)NULL)) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } mstate.Assign(state); mstate.Reshape(HistoryBars, BarDescr); mstate.Resize(NForecast, ForecastBarDescr); bStateD.AssignArray(mstate); if(!Decoder.feedForward((CBufferFloat*)GetPointer(bStateD), class="num">1, class="kw">false, GetPointer(Encoder))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true;