交易中的神经网络:基于 ResNeXt 模型的多任务学习
概述
人工智能的快速发展导致了深度学习方法与数据分析的积极整合,包括金融板块。金融输入具有高维度、异质性、和时态结构,这令传统处理方法的应用变得复杂。同时,深度学习在分析复杂和非结构化数据方面演绎出高效率。
在潮流卷积架构中,有一个特别突出:ResNeXt,该架构在《深度神经网络的聚合残差变换》一文中有所讲述。ResNeXt 具备捕捉局部和全局依赖关系的能力,并有效处理多维数据,同时经由分组卷积降低计算复杂度。
利用深度学习进行金融分析的一个关键领域是多任务学习(MTL)。该方式允许同时解决多个相关任务,提升模型的准确性和普适能力。不同于经典方式中每个模型只定位单一任务,MTL 利用共享数据表示,令模型对市场波动更具韧性,并强化训练过程。该方式对于市场趋势预测、风险评估、和资产估值尤其宝贵,在于金融市场动态变化,且受多种因素影响。
《经由深度学习和 ResNeXt 在金融数据挖掘中的协同优化》的研究中讲述了一个 ResNeXt 架构整合到多任务模型中的框架。该方案为处理时间序列、识别时空形态、及生成准确预测开辟了新可能。ResNeXt 的分组卷积和残差模块加速训练,降低了关键特征丢失的风险,令该方法尤为适用金融分析。
该方式的另一个显著优势是能自动从原产数据中提取有意义特征。传统的金融分析通常需要大量的特征工程,而深度神经网络则能够自主识别关键形态。这在分析多模态金融数据时尤为重要,因为其涉及市场指标、宏观经济报告、和新闻发布、等多个来源。MTL 的灵活性允许动态调整任务权重、及损失函数,提升模型对市场变化的适应性、以及预测准确性。
ResNeXt 架构
ResNeXt 架构基于模块化设计、及分组卷积。其核心是含有残差连接的卷积模块,受两个关键原则管辖:
- 如果输出特征映射具有相同大小,模块使用雷同的超参数(宽度和滤波器大小)。
- 如果特征映射大小萎缩,模块宽度会按比例增加。
遵循这些原则可维持所有模型层的计算复杂度大致恒定,简化设计。如此定义一个模板模块就足够了,其它模块则自动生成,确保标准化、更易调优、以及流水线架构分析。
人工神经网络中的标准神经元执行输数据入加权求和,这是卷积层和全连接层的主要运算。这一过程能被划分为三个阶段:拆分、变换、和聚合。ResNeXt 引入了一个更灵活的方式,允许变换函数更复杂,甚至是微模型本身。这就催生了神经网络概念,经由一个新的维度:基数性,扩展了架构的能力。不同于宽度或深度,基数性判定每个模块内独立、复变换的数量。实验表明,增加基数性较之增加深度或宽度更有效,从而在性能和计算效率之间提供更佳平衡。
所有 ResNeXt 模块共享统一的瓶颈结构。其组成有:
- 一个初始 1×1 卷积层,降低特征维度,
- 一个主 3×3 卷积层,执行核心数据处理,
- 最后一个 1×1 卷积层恢复原始维度。
这种设计降低了计算复杂度,同时维持了模型的高表现力。此外,残余连接在训练期间保留梯度,预防梯度消散,这也是深度网络的关键因素。
ResNeXt 的一个重大强化是使用了分组卷积。于此,输入数据被划分为多个独立分组,每个分组由单独的卷积滤波器处理,结果随后被聚合。这减少了模型参数,维持了网络高吞吐量,并提升了计算效率,没有明显的准确性损失。
为了在更改组数时维持稳定的计算复杂度,ResNeXt 调整瓶颈模块的宽度,控制内层的通道数量。这令伸缩模型,且计算开销不至于过份。
基于 ResNeXt 的多任务学习框架代表了一种进步的及内容数据处理方式,跨越各种分析任务的共享特征利用、协作建模。它由三个关键结构化元件组成:
- 特征提取模块,
- 共享学习模块,
- 任务特定的输出层。
该方式将高效的深度学习机制,与金融时间序列相结合,衍生出高预测精度,并适应动态市场条件的模型。
特征提取模块依赖于 ResNeXt 架构,有效捕捉金融数据的局部和全局特征。在多维金融数据中,关键参数是模型中的分组数量。它权衡了详细特征表示,及计算成本。ResNeXT 中的每个分组卷积识别通道分组中的特定形态,然后把它们聚合成统一表示。
送经非线性变换层之后,所提取特征形成了后续多任务学习和任务特定适配的基础。共享学习模块采用一种权重共享机制,将共同特征投射到任务特定的空间之中。这就确保模型能够为每个任务生成独立表示,同时避免干扰,达成高度特征共享效率。基于相关性的任务聚类进一步强化了共享学习机制。
任务特定的输出层由全连接感知器组成,将专业特征投射到最终的预测空间。输出层能够适配每个任务的性质。具体而言,分类任务使用交叉熵损失,而回归任务使用均方误差(MSE)。对于多任务学习,最终损失函数表示为单个任务损失的加权和。
训练在多个阶段发生。最初,模型会针对单个任务进行预训练,以确保专业 MLP 的有效收敛。随后,模型在多任务架构中进行优调,以提升整体性能。优化使用具备动态学习率调整的 Adam 算法。
实现 MQL5 版本
在回顾了基于 ResNeXt 的多任务学习框架的理论层面后,我们开始按我们的诠释实现 MQL5 版本。我们将从构造基础的 ResNeXt 架构模块开始 — 瓶颈模块。
瓶颈模块
瓶颈模块由三层卷积层组成,每层都在处理原产数据中发挥关键作用。第一层降低特征维度,拉低后续处理的计算复杂度。
第二卷积层执行主要卷积,提取准确解读数据所需的复杂高级特征。它分析元素间的相互依赖性,识别后期关键的形态。该方式令模型能够适应金融数据中的非线性依赖性,从而提升预测的准确性。
最后一层恢复了原始张量维度,保留了所有重要信息。早期特征提取时沿时间轴降维,通过扩展特征空间得到补偿,这与 ResNeXt 原则一致。
为了稳定训练,每个卷积层都后随批量归一化。它降低内部协变量偏移,并加速收敛。ReLU 激活强化模型的非线性,提升了捕捉复杂依赖度和普适品质的能力。
上述架构在 CNeuronResNeXtBottleneck 对象中实现,其结构如下。
class CNeuronResNeXtBottleneck : public CNeuronConvOCL { protected: CNeuronConvOCL cProjectionIn; CNeuronBatchNormOCL cNormalizeIn; CNeuronTransposeRCDOCL cTransposeIn; CNeuronConvOCL cFeatureExtraction; CNeuronBatchNormOCL cNormalizeFeature; CNeuronTransposeRCDOCL cTransposeOut; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronResNeXtBottleneck(void){}; ~CNeuronResNeXtBottleneck(void){}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronResNeXtBottleneck; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual CLayerDescription* GetLayerInfo(void) override; virtual void SetOpenCL(COpenCLMy *obj) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
作为父类,我们用到一个卷积层对象,它执行恢复特征空间的功能。此外,该结构还包含多个内部对象,每个对象都在我们构造的算法中被赋予关键角色。随着我们构建新类的方法,将会详细披露它们的功能。
所有内部对象都声明为静态,允许我们将类构造和析构函数留空。这些声明和继承对象的初始化均在 Init 方法中执行。该方法接收一组常量,明确定义所创建模块的架构。
bool CNeuronResNeXtBottleneck::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch) { int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1; if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, group_size * groups, group_size * groups, chanels_out, units_out, 1, optimization_type, batch)) return false;
在方法主体中,我们通常调用父类的同名方法,其中包含继承对象和接口的初始化算法。然而,在这种情况下,父类作为模块的最终卷积层。其输入接收提取后的特征数据,在处理期间张量的维数或许会发生变化。因此,我们首先在模块输出处判定序列长度,然后才会调用父类方法。
继承对象初始化成功后,我们处理新声明的对象。工作从数据投影模块开始。第一卷积层按所需工作组数量准备原产数据的投影。
//--- Projection In uint index = 0; if(!cProjectionIn.Init(0, index, OpenCL, chanels_in, chanels_in, group_size * groups, units_count, 1, optimization, iBatch)) return false; index++; if(!cNormalizeIn.Init(0, index, OpenCL, cProjectionIn.Neurons(), iBatch, optimization)) return false; cNormalizeIn.SetActivationFunction(LReLU);
这些投影随后调用 LReLU 函数进行归一化和激活。
注意这些操作的结果是一个三维张量 [时间, 分组, 维度]。为了实现各个分组的独立处理,我们使用一个 3D 张量置换对象将分组标识符维度移至第一位置。
index++; if(!cTransposeIn.Init(0, index, OpenCL, units_count, groups, group_size, optimization, iBatch)) return false; cTransposeIn.SetActivationFunction((ENUM_ACTIVATION)cNormalizeIn.Activation());
接下来是特征提取模块。于此,我们用到一个卷积层,指定分组数量作为独立序列的数量。这就确保了各个分组的数值不会“混合”。每个分组使用自己的可训练参数矩阵。
//--- Feature Extraction index++; if(!cFeatureExtraction.Init(0, index, OpenCL, group_size * window, group_size * step, group_size, units_out, groups, optimization, iBatch)) return false;
此外,注意该方法还接收卷积窗口大小、及其在时态维度上的步长、等参数。当把这些参数传递给内部卷积层初始化时,我们将相应的数值乘以分组大小。
卷积层之后,我们加入配备 LReLU 激活函数的批次归一化。
index++; if(!cNormalizeFeature.Init(0, index, OpenCL, cFeatureExtraction.Neurons(), iBatch, optimization)) return false; cNormalizeFeature.SetActivationFunction(LReLU);
最终的特征空间向后投影模块仅由一个 3D 张量置换对象组成,将分组合并至单一序列。如早前所述,实际的数据投影是调用父类继承方法来执行。
//--- Projection Out index++; if(!cTransposeOut.Init(0, index, OpenCL, groups, units_out, group_size, optimization, iBatch)) return false; cTransposeOut.SetActivationFunction((ENUM_ACTIVATION)cNormalizeFeature.Activation()); //--- return true; }
现在我们只需将操作的逻辑结果返回给调用者,并完结对象初始化方法。
下一步是构建前馈通验算法,该算法在 feedForward 方法中实现。
bool CNeuronResNeXtBottleneck::feedForward(CNeuronBaseOCL *NeuronOCL) { //--- Projection In if(!cProjectionIn.FeedForward(NeuronOCL)) return false;
该方法接收到指向原产数据对象的指针,其会立即传递给数据投影模块第一个内部卷积层中的同名方法。我们不检查指针的有效性,在于该验证已由内部层处理,这令额外的检查变得多余。
投影结果被归一化,并转置为独立的分组表示。
if(!cNormalizeIn.FeedForward(cProjectionIn.AsObject())) return false; if(!cTransposeIn.FeedForward(cNormalizeIn.AsObject())) return false;
在特征提取模块中,执行分组卷积操作,并将结果归一化。
//--- Feature Extraction if(!cFeatureExtraction.FeedForward(cTransposeIn.AsObject())) return false; if(!cNormalizeFeature.FeedForward(cFeatureExtraction.AsObject())) return false;
从单个分组中提取的特征会被重新置换回单一多维序列,并经父类投影到指定的特征空间。
//--- Projection Out if(!cTransposeOut.FeedForward(cNormalizeFeature.AsObject())) return false; return CNeuronConvOCL::feedForward(cTransposeOut.AsObject()); }
这些操作的逻辑结果会返回至调用程序,并结束方法。
如您所见,前馈算法是线性的。误差梯度传播也是线性的。因此,反向传播方法留待独立研究。该对象及其所有方法的完整代码已在附件中提供。
残余连接模块
ResNeXt 架构为每个瓶颈模块提供残差连接,在反向传播期间,其促进了高效误差梯度传播。这些连接允许模型重用先前提取的特征,提升收敛性,并降低梯度消散的风险。如是结果,模型能被训练到更深层次,而不会显著增加计算成本。
重点要注意,瓶颈模块的输出张量大致维持相同的整体大小,但各个维度或许会有变化。时态步长的减少会由特征空间维度的增加所补偿,保留关键信息,并捕捉长期依赖关系。一个独立的投影模块通过将输入数据映射到所需维度,确保残余连接的正确集成。这可防止不匹配,即使在深度架构中也能维持训练稳定性。
在我们的实现中,我们所创建的模块作为 CNeuronResNeXtResidual 对象,其结构如下所示。
class CNeuronResNeXtResidual: public CNeuronConvOCL { protected: CNeuronTransposeOCL cTransposeIn; CNeuronConvOCL cProjectionTime; CNeuronBatchNormOCL cNormalizeTime; CNeuronTransposeOCL cTransposeOut; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronResNeXtResidual(void){}; ~CNeuronResNeXtResidual(void){}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint units_in, uint units_out, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronResNeXtResidual; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual CLayerDescription* GetLayerInfo(void) override; virtual void SetOpenCL(COpenCLMy *obj) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
在开发该对象时,我们所用的方式类似于构造瓶颈模块,但针对输入张量的不同维度进行了适配。
在呈现的对象结构中,您能见到若干个嵌套对象 — 它们的功能将在新类方法的实现期间讲述。所有内部对象均声明为静态。这就允许我们将类的构造和析构函数留空。所有对象,包括继承对象的初始化都在 Init 模块中执行,其接收一套定义对象架构的常量。
bool CNeuronResNeXtResidual::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint units_in, uint units_out, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, chanels_in, chanels_in, chanels_out, units_out, 1, optimization_type, batch)) return false;
在方法内,我们首先调用父类的同名方法,传递必要的参数。正如瓶颈模块,我们用到一个卷积层作为父类。它还处理数据投影到新特征空间。
继承对象和接口初始化成功后,我们转到处置新声明的对象。为了方便协同时态维度工作,我们首先转置输入数据。
int index=0; if(!cTransposeIn.Init(0, index, OpenCL, units_in, chanels_in, optimization, iBatch)) return false;
接下来,卷积层将单个单元序列投影到指定的维度之中。
index++; if(!cProjectionTime.Init(0, index, OpenCL, units_in, units_in, units_out, chanels_in, 1, optimization, iBatch)) return false;
结果会被归一化,类似于瓶颈模块。然而,未应用激活函数,在于残差模块必须无损传递所有信息。
index++; if(!cNormalizeTime.Init(0, index, OpenCL, cProjectionTime.Neurons(), iBatch, optimization)) return false;
然后我们调整特征空间。为此,我们执行逆置换。投影随后由父类处理。
index++; if(!cTransposeOut.Init(0, index, OpenCL, chanels_in, units_out, optimization, iBatch)) return false; //--- return true; }
其余所有需要我们做的,就是将操作的逻辑结果返回给调用程序,并完结新对象初始化方法的工作。
接下来,我们转到 feedForward 方法构造前馈通验算法。
bool CNeuronResNeXtResidual::feedForward(CNeuronBaseOCL *NeuronOCL) { //--- Projection Timeline if(!cTransposeIn.FeedForward(NeuronOCL)) return false;
该方法接收到指向包含输入数据的对象指针。该指针会被传递给内部数据置换层,其将数据转换为单位序列。
接下来,我们通过卷积层将单位序列的维度调整到目标尺寸。
if(!cProjectionTime.FeedForward(cTransposeIn.AsObject())) return false;
结果会被归一化。
if(!cNormalizeTime.FeedForward(cProjectionTime.AsObject())) return false;
最后,应用逆置换,并将数据投影到特征空间之中。
//--- Projection Chanels if(!cTransposeOut.FeedForward(cNormalizeTime.AsObject())) return false; return CNeuronConvOCL::feedForward(cTransposeOut.AsObject()); }
最终投影由父类执行。操作的逻辑结果随后返回给调用程序,完成前馈方法。
前馈通验算法是线性的。因此,在反向传播期间,我们得到一条线性梯度流。故此,反向传播方法留给独立研究,类似于 CNeuronResNeXtBottleneck 对象。这些对象及其所有模块的完整代码已在附件中提供。
ResNeXt 模块
上文,我们创建了表达 ResNeXt 框架两条信息流的独立对象。现在是时候将这些对象合并为一个结构,以实现更高效的数据处理。为此目的,我们创建了 CNeuronResNeXtBlock 对象,作为后续数据处理的主要模块。该对象的结构呈现如下。
class CNeuronResNeXtBlock : public CNeuronBaseOCL { protected: uint iChanelsOut; CNeuronResNeXtBottleneck cBottleneck; CNeuronResNeXtResidual cResidual; CBufferFloat cBuffer; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronResNeXtBlock(void){}; ~CNeuronResNeXtBlock(void){}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronResNeXtBlock; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual CLayerDescription* GetLayerInfo(void) override; virtual void SetOpenCL(COpenCLMy *obj) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
在该结构中,我们见到熟悉的对象,和一套标准的虚拟方法,它们都需要我们去覆盖。
所有内部对象都声明为静态,允许我们将类构造和析构函数留空。这些声明和继承对象的初始化均在 Init 方法中执行。其参数结构完全继承自 CNeuronResNeXtBottleneck 对象。
bool CNeuronResNeXtBlock::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch) { int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1; if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, units_out * chanels_out, optimization_type, batch)) return false;
在方法主体中,我们首先确判定模块输出处的序列维度,然后初始化从父对象继承的基础接口。
父类初始化方法执行成功后,我们将必要的参数保存到对象的变量当中。
iChanelsOut = chanels_out;
我们初始化先前构造的信息流的内部对象。
int index = 0; if(!cBottleneck.Init(0, index, OpenCL, chanels_in, chanels_out, window, step, units_count, group_size, groups, optimization, iBatch)) return false; index++; if(!cResidual.Init(0, index, OpenCL, chanels_in, chanels_out, units_count, units_out, optimization, iBatch)) return false;
在模块输出处,我们期望接收两条信息流的数值之和。因此,接收到的误差梯度能够完全传播到两条数据流。为避免不必要的数据复制,交换相应数据缓冲区的指针。
if(!cResidual.SetGradient(cBottleneck.getGradient(), true)) return false; if(!SetGradient(cBottleneck.getGradient(), true)) return false; //--- return true; }
最后,我们返回一个布尔结果至调用程序,并完结初始化方法。
接下来,我们在 feedForward 方法中构建前馈通验算法。此处的一切都十分简单。
bool CNeuronResNeXtBlock::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!cBottleneck.FeedForward(NeuronOCL)) return false; if(!cResidual.FeedForward(NeuronOCL)) return false;
该方法接收指向输入数据对象的指针,其会被立即传递给两条信息流对象的方法。然后汇总结果,并归一化。
if(!SumAndNormilize(cBottleneck.getOutput(), cResidual.getOutput(), Output, iChanelsOut, true, 0, 0, 0, 1)) return false; //--- result return true; }
操作的逻辑结果随后返回给调用程序,完成前馈方法。
虽然该结构乍看之下看似简单,但实际上包含了两条信息流,增加了误差梯度分布的复杂性。负责该过程的算法在 calcInputGradients 方法中实现。
bool CNeuronResNeXtBlock::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) return false;
该方法接收一个指向输入数据对象的指针,其会在前馈通验期间用到。在这种情况下,误差梯度必须根据输入数据对最终模型输出的影响传播。数据仅能传递给有效对象。因此,在继续操作之前,我们首先检查接收指针的相关性。
通过该验证后,误差梯度会经由第一条信息流传播。
if(!NeuronOCL.calcHiddenGradients(cBottleneck.AsObject())) return false;
在将梯度传播到第二条流之前,必须保留先前获得的数据。我们采用了指针交换机制,而非完全复制数据。输入数据误差梯度缓冲区的指针被保存在局部变量之中。
CBufferFloat *temp = NeuronOCL.getGradient();
接下来,我们检查辅助缓冲区是否与梯度缓冲区的尺寸相匹配。必要时调整。
if(cBuffer.GetOpenCL() != OpenCL || cBuffer.Total() != temp.Total()) { if(!cBuffer.BufferInitLike(temp)) return false; }
然后其指针被传递给输入数据对象。
if(!NeuronOCL.SetGradient(GetPointer(cBuffer), false)) return false;
现在,误差梯度可以安全地经由第二条信息流传播,而不会有数据丢失的风险。
if(!NeuronOCL.calcHiddenGradients(cResidual.AsObject())) return false;
将两条流的值相加,缓冲指针恢复到原始状态。
if(!SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1)) return false; if(!NeuronOCL.SetGradient(temp, false)) return false; //--- return true; }
然后我们将操作的逻辑结果返回给调用者,并完结误差梯度分派方法。
构建 ResNeXt 模块对象方法算法的概览至此完毕。该对象及其所有方法的完整代码已在附件中提供。
本文已达尾声,但我们的工作尚未完成。我们将稍事停顿,并在下一篇文章中继续。
结束语
本文讲述了一个基于 ResNeXt 架构的多任务学习框架,专为处理金融数据而设计。该框架实现了高效的特征提取和处理,优化了高维和时间序列数据环境中的分类和回归任务。
在实践部分,我们构造了 ResNeXt 架构的主要元素。在下一篇文章中,我们将构建多任务学习框架,并评估所实现方法在真实历史数据上的有效性。
参考
文章中所用程序
| # | 名称 | 类型 | 说明 |
|---|---|---|---|
| 1 | Research.mq5 | 智能系统 | 收集样本的智能系统 |
| 2 | ResearchRealORL.mq5 | 智能系统 | 利用 Real-ORL 方法收集样本的智能系统 |
| 3 | Study.mq5 | 智能系统 | 模型训练智能系统 |
| 4 | Test.mq5 | 智能系统 | 模型测试智能系统 |
| 模型测试智能系统 | Trajectory.mqh | 类库 | 系统状态和模型架构描述结构 |
| 6 | NeuroNet.mqh | 类库 | 创建神经网络的类库 |
| 7 | NeuroNet.cl | 函数库 | OpenCL 程序代码 |
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17142
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
斐波那契(Fibonacci)数列在外汇交易中的应用(第一部分):探究价格与时间的关系
MQL5 MVC模式中表格的视图组件:基础图形元素
新手在交易中的10个基本错误
使用 MetaTrader 5 Python 构建类似 MQL5 的交易类
根据我的经验,能够分享真正有用的东西的交易者从来不会分享任何东西。
是的,他们知道(就像我从 1998 年开始就知道),一个有效的策略在发布后很快就会失效。
这就是为什么论坛上的程序员会分享个人解决方案,而有效的(盈利的)策略却从未发布过。或出售。
是的,他们知道(我从 1998 年开始就知道),一个有效的战略一旦传播开来,很快就会失效。
这就是为什么论坛上的程序员会分享各自的解决方案,而有效的(盈利的)策略却从未公布过。或出售。
国家间转账的需求就不算数了吗?)
你怎么能成为这样的系统?
如果你在回调时买入,交易机器人就会一直工作,问题是回调在哪里?
我看过译文,我绝对不能翻译
我得承认,我不够聪明,看不懂原文。
"我整晚都在自言自语,他们却听不懂!"(日瓦涅茨基
是的,他们知道(我从 1998 年开始就知道),有效的策略一旦传播开来,很快就会失效。
这适用于流动性有限的交易所,但不适用于外汇交易,那里有足够的流动性供所有人使用。
附注:我想起了米哈伊尔,他在莫斯科交易所有一套对冲系统,他分享了这套系统,它很有效,将来也应该有效。一切都取决于个人资本,100 美元在那里什么也做不了。
在这里,每个人都在寻找一个100英镑的系统,每天盈利10%。这就是搜索结果如此糟糕的原因。