繁簡切換您正在訪問的是FX168財經網,本網站所提供的內容及信息均遵守中華人民共和國香港特別行政區當地法律法規。

FX168财经网>人物频道>帖子

神经网络变得轻松(第九部分):操作归档

作者/量化王者 2021-03-16 19:00 0 来源: FX168财经网人物频道

内容

  • 概述
  • 1. 创建文档的基本原则
  • 2. 选择工具
  • 3. 在代码中归档
  • 4. 代码源文件中的准备
  • 5. 生成文档
  • 结束语
  • 参考
  • 本文中用到的程序

概述

在之前文章中,我们一直在添加新对象,并扩展现有对象的功能。 所有这些增加扩展了我们的函数库。 我们还加入了一个 OpenCL 程序文件。 现在,代码比最初版本大了 10 多倍。 在代码中跟踪对象之间的关系变得越来越困难。 读者可能会发现,阅读代码时感觉非常混乱,且难以理解。 我尝试在每篇文章中都提供对动作逻辑的详述。 但是,演示单独动作链,并不能提供对该程序的全面理解。

这就是为什么我决定演示如何创建代码文档,从而可从另一个角度查看代码。 本文档的目的是概括函数库中的所有对象和方法,并建立对象和方法的继承层次结构。 这应该使我们对所做的事情有一个大致的了解。


1. 创建文档的基本原则

技术文档在 IT 开发中的目的是什么? 首先,文档能给出程序体系结构和操作的全面概念。 正确的文档令开发团队能够正确地区分责任范围,跟踪代码中的所有变更,并评估其对整个算法和体系结构完整性的影响。 它还有助于知识共享。 理解程序体系结构的完整性,可分析和制定项目开发的方式。

正确编写的技术文档应考虑到其目标用户的资质。 信息应清晰,并应避免过多的解释。 文档应包括用户需要的所有信息。 于此同时,它应该简明易懂。 过多的内容会浪费额外的时间来阅读,并令读者反感。 如果用户即使阅读过冗长的文档,却依然找不到所需的信息,则更令人烦恼。 这会导致下一条规则:文档必须具有便捷的信息搜索工具。 用户友好的界面和交叉引用,令查找所需的信息变得更加轻松。

文档应包含解决方案的完整体系结构,以及已实现的技术解决方案的说明。 完整而详细的解决方案描述有助于开发和进一步的支持。 始终随时保持文档更新是非常重要的。 过时的信息可能导致管理决策矛盾,结果可能会令整体开发失衡。

而且,文档必须必须描述组件之间的接口。


2. 选择工具

有一些专用程序可以帮助创建文档。 我认为,最常见的是 Doxygen,Sphinx,Latex(还有其他一些工具)。 所有这些目标旨在降低人工创建文档的成本。 当然,每个程序都是由开发人员创建的,用于解决特定问题。 例如,Doxygen 是为 C++ 程序和类似编程语言创建文档的程序。 Sphinx 是为 Python 文档创建的。 但这并不意味着它们仅限于一种编程语言方面非常专业。 这两个程序都可以与各种编程语言一起很好地工作。 相关程序网站提供了有关它们如何使用的详细参考,故您可以选择最适合自己的一种。

前面已经在文章“自动创建 MQL5 程序的文档”中讨论过 MQL5 的文档。 本文建议采用 Doxygen。 我还将这个程序用于我的开发。 MQL5 语法接近 C++,因此 Doxygen 非常适合 MQL5 程序。 我喜欢的事实:为了创建文档,您只需要在程序代码中添加相应的注释,而其余的工作则由专用软件来完成。 甚而,Doxygen 允许插入超链接和数学公式,这对于文章的主题而言非常重要。 在本文中,我们将采用特定的示例,进一步研究特定功能的用法。


3. 在代码中归档

如上所述,为了生成文档,您需要在程序代码中添加注释。 Doxygen 则根据这些注释创建文档。 自然,并非所有代码注释都应包含在文档之中。 一些注释可能包含开发人员批注,在某些地方还为没用到的代码添加了注释。 Doxygen 开发人员提供了一些方式来标记包含在文档中的注释。 有若干种选择,您可为自己选择一种最方便的。

与 MQL5 类似,文档注释可以是单行和多行。 为了避免将来直接引用代码时不会受到干扰,我们将采用标准选项来插入注释;对于单行注释,我们将采用一个额外的斜杠;对于多行注释,我们将附加一个星号。 可选地,还可用感叹号来标识文档注释块。

/// A single-line comment for documentation
/** A multi-line block for documentation
*/

//! An alternative single-line comment for documentation
/*! An alternative
    multi-line
    block for 
    documentation
*/

请注意,多行注释块并不意味着在文档里一定要出现相同的多行。 如果您需要分隔程序对象的简述和详述,则您可以添加不同的注释块或使用特殊命令,这些命令由这些字符表示 "\" 或 "@"。 命令 "\n" 可用来强制换行。

选项 1: 分离的块
/// Short description
/** Detailed description
*/

选项 2: 特殊命令的用法
/** \brief Brief description
    \details Detailed description
*/ 

通常,假定文档对象位于文件中的注释块旁边。 但实际上,可能位于注释块之前位于的对象需要进行注释。 在这种情况下,使用字符 "<" 通知 Doxygen 注释的对象位于该块之前。 若要在注释中创建交叉引用,则在引用对象之前添加 "#"。 下面是代码示例,以及在文档中生成的代码块。 在生成的模板中,“CConnection” 是一个引用,指向相应类的文档页面。

#define defConnect         0x7781   ///<Connection \details Identified class #CConnection


Doxygen 功能很广泛。 命令的完整列表及其说明可在程序页面的文档板块下找到。 甚而,Doxygen 还能理解 HTML 和 XML 标记。 所有这些功能在创建文档时都可解决各种任务。


4. 代码源文件中的准备

至此,我们已经回顾了工具的功能,我们可以开始处理文档。 首先,我们来描述一下我们的文件。

/// \file
/// \brief NeuroNet.mqh
/// Library for creating Neural network for use in MQL5 experts
/// \author [DNG](https://www.mql5.com/en/users/dng)
/// \copyright Copyright 2019, DNG

/// \file
/// \brief NeuroNet.cl
/// Library consist OpenCL kernels
/// \author <A HREF="https://www.mql5.com/en/users/dng"> DNG </A>
/// \copyright Copyright 2019, DNG

请注意,在第一种情况下,\author 指针后跟 Doxygen 提供的标记;在第二种情况下,用的是 HTML 标记。 此处用它是为演示不同选项,譬如创建超链接。 在这些情况下,结果是相同的 - 它在 MQL5.com 上创建了指向我个人资料的链接。

 

当然,在开始创建代码文档时,必须至少已拥有所需结果的高级结构。 对最终结构的理解可以对文档对象进行正确的分组。 我们将所创建枚举合并到一个分离的组中。 为了声明一个组,使用 “\defgroup” 命令。 组的边界可用字符 "@{" 和 "@}" 表示。

///\defgroup enums ENUM
///@{
//+------------------------------------------------------------------+
/// Enum of activation formula used      
//+------------------------------------------------------------------+
enum ENUM_ACTIVATION
  {
   None=-1, ///< Without activation formula
   TANH,    ///< Use \f$tanh(x)\f$ for activation neuron
   SIGMOID, ///< Use \f$\frac{1}{1+e^x}\f$ fo activation neuron
   LReLU    ///< For activation neuron use LReLU \f[\left\{ \begin{array} a x>=0, \ x \\x<0, \ 0.01*x \end{array} \right.\f]
  };
//+------------------------------------------------------------------+
/// Enum of optimization method used      
//+------------------------------------------------------------------+
enum ENUM_OPTIMIZATION
  {
   SGD,  ///< Stochastic gradient descent
   ADAM  ///< Adam
  };
///@}

在描述激活函数时,我曾演示过通过 MathJax 声明数学公式的功能。 如果希望在文本行中显示公式,则应在一对 “\f$” 命令之间放置此类公式的说明,或者,若您希望公式显示在单独行上,则放置于命令 “\f[ 和 "\f]" 之间。 "\frac" 命令能够描述分数。 该命令之后,在大括号中是小数的分子和分母。

描述 LReLU 时,我们需要一个统一的左大括号。 为了创建它,我们使用了命令 "\left\{" 和 "\right\."。 “\right” 命令后跟 “\.”,因为公式中不需要右大括号。 否则,句号将被一个大括号代替。 使用命令 "\begin{array} a" 和 "\end{array}" 在块内声明字符串数组,数组元素则是由 "\\" 命令执行分隔的。 "\ " 字符能够添加强制空格。

生成的文档块如下所示。


在下一步中,我们为函数库中的类标识符创建一个单独的组。 在该组内,我们将分配数组的子组,在 CPU 上计算神经元的运算,和在 GPU 上计算神经元的运算。 如早前所述,添加了指向相应类的链接。

///\defgroup ObjectTypes  Defines Object types identified
///Used to identify classes in a library
///@{                                
//+------------------------------------------------------------------+
///\defgroup arr Arrays
///Used to identify array classes
///\{
#define defArrayConnects   0x7782   ///<Array of connections \details Identified class #CArrayCon
#define defLayer           0x7787   ///<Layer of neurons \details Identified class #CLayer
#define defArrayLayer      0x7788   ///<Array of layers \details Identified class #CArrayLayer
#define defNet             0x7790   ///<Neuron Net \details Identified class #CNet
///\}
///\defgroup cpu CPU
///Used to identify classes with CPU calculation
///\{
#define defConnect         0x7781   ///<Connection \details Identified class #CConnection
#define defNeuronBase      0x7783   ///<Neuron base type \details Identified class #CNeuronBase
#define defNeuron          0x7784   ///<Full connected neuron \details Identified class #CNeuron
#define defNeuronConv      0x7785   ///<Convolution neuron \details Identified class #CNeuronConv
#define defNeuronProof     0x7786   ///<Proof neuron \details Identified class #CNeuronProof
#define defNeuronLSTM      0x7791   ///<LSTM Neuron \details Identified class #CNeuronLSTM
///\}
///\defgroup gpu GPU
///Used to identify classes with GPU calculation
///\{
#define defBufferDouble    0x7882   ///<Data Buffer OpenCL \details Identified class #CBufferDouble
#define defNeuronBaseOCL   0x7883   ///<Neuron Base OpenCL \details Identified class #CNeuronBaseOCL
#define defNeuronConvOCL   0x7885   ///<Convolution neuron OpenCL \details Identified class #CNeuronConvOCL
#define defNeuronProofOCL  0x7886   ///<Proof neuron OpenCL \details Identified class #CNeuronProofOCL
#define defNeuronAttentionOCL 0x7887   ///<Attention neuron OpenCL \details Identified class #CNeuronAttentionOCL
///\}
///@}

 生成的文档中的分组如下所示。

接下来,我们将研究操控 OpenCL 内核的大量定义组。 在该块中,将助记符名称分配给内核索引及其参数,从主程序调用内核时会用到这些索引。 采用上述技术,我们将依据调用内核神经元的类别、以及内核中操作的内容(前馈,梯度反向传播,更新权重系数)来分组。 我不会在此处提供完整的代码 - 可从下面的附件中找到它们。 构造子组的逻辑与上面的示例相似。 下面的截屏展示了完整的组结构。 


继续介绍内核,我们转至正评述的 OpenCL 程序。 为了创建连贯的文档结构,并获得全面印象,我们将利用另一个 Doxygen 命令 “\ingroup,该命令能够将新的文档对象添加到之前创建的组中。 我们用它将内核添加到早前创建的索引分组当中,以便操控内核。 在内核描述中,添加指向调用类的链接,以及指向该站点上带有过程描述的文章链接。 接下来,我们来描述内核参数。 指针 “[in]” 和 “[out]” 的用法将显示信息流的方向。 交叉引用将显示数据格式。

///\ingroup neuron_base_ff Feed forward process kernel
/// Describes the forward path process for the Neuron Base (#CNeuronBaseOCL).
///\details Detailed description on <A HREF="https://www.mql5.com/en/articles/8435#para41">the link.</A>
//+------------------------------------------------------------------+
__kernel void FeedForward(__global double *matrix_w,///<[in] Weights matrix (m+1)*n, where m - number of neurons in layer and n - number of outputs (neurons in next layer)
                          __global double *matrix_i,///<[in] Inputs tesor
                          __global double *matrix_o,///<[out] Output tensor
                          int inputs,///< Number of inputs
                          int activation///< Activation type (#ENUM_ACTIVATION)
                          )

上面的代码将生成以下文档块。


在上面的示例中,参数声明之后立即给出其·说明。 但是这种方式会令代码变得笨拙。 在这种情况下,建议使用 “\param” 命令来描述参数。 通过使用此命令,我们可在文件的任何部分中描述参数,但是我们需要直接指定参数名称。

///\ingroup neuron_atten_gr Attention layer's neuron Gradients Calculation kernel
/// Describes the gradients calculation process for the Neuron of attention layer (#CNeuronAttentionOCL).
///\details Detailed description on <A HREF="https://www.mql5.com/ru/articles/8765#para44">the link.</A>
/// @param[in] querys Matrix of Querys
/// @param[out] querys_g Matrix of Querys' Gradients
/// @param[in] keys Matrix of Keys
/// @param[out] keys_g Matrix of Keys' Gradients
/// @param[in] values Matrix of Values
/// @param[out] values_g Matrix of Values' Gradients
/// @param[in] scores Matrix of Scores
/// @param[in] gradient Matrix of Gradients from previous iteration
//+------------------------------------------------------------------+
__kernel void AttentionIsideGradients(__global double *querys,__global double *querys_g,
                                      __global double *keys,__global double *keys_g,
                                      __global double *values,__global double *values_g,
                                      __global double *scores,
                                      __global double *gradient)

这种方式生成一个类似的文档块,但它能够将注释块与程序代码分离。 因此,代码变得更易于阅读。

 

主要工作涉及我们的函数库类及其方法的文档。 我们需要描述所有用到的类及其方法。 为此,我们将利用上述所有不同形式的命令,并添加一些新命令。 首先,我们将类添加到相应的组中,就像我们早前针对内核那样(\ingroup 命令)。 "\class" 命令通知 Doxygen 下述需应用于该类。 在命令参数中,指定类名称,从而将描述链接到正确的对象

利用 "\brief" 和 "\details" 命令,提供类的简述和扩展描述。 在详述之中,加入相应文章的链接。 此处,我们将在文章的特定章节添加一个锚链接,如此可令用户能够更快地找到所需的信息。

将它们的描述直接添加到变量声明行。 如有必要,则添加指向对象说明的链接。 无需在注释中设置指向已声明对象类的指针,而 Doxygen 会自动添加它们。

类的描述方法与此类似。 不过,与变量不同,应在注释中添加参数描述。 为此,利用前面介绍的 “\param” 命令,以及 “[in]”,“[out]”,“[in,out]” 指针。 利用 “\return” 命令描述方法的执行结果。

它也可以通过某些功能将独立方法附加到组中。 例如,它们可按功能组合。

下面的代码展示了上述所有步骤。 

///\ingroup neuron_base
///\class CNeuronBaseOCL
///\brief The base class of neuron for GPU calculation. 
///\details Detailed description on <A HREF="https://www.mql5.com/ru/articles/8435#para45">the link.</A>
//+------------------------------------------------------------------+
class CNeuronBaseOCL    :  public CObject
  {
protected:
   COpenCLMy         *OpenCL;             ///< Object for working with OpenCL
   CBufferDouble     *Output;             ///< Buffer of Output tenzor
   CBufferDouble     *PrevOutput;         ///< Buffer of previous iteration Output tenzor
   CBufferDouble     *Weights;            ///< Buffer of weights matrix
   CBufferDouble     *DeltaWeights;       ///< Buffer of last delta weights matrix (#SGD)
   CBufferDouble     *Gradient;           ///< Buffer of gradient tenzor
   CBufferDouble     *FirstMomentum;      ///< Buffer of first momentum matrix (#ADAM)
   CBufferDouble     *SecondMomentum;     ///< Buffer of second momentum matrix (#ADAM)
//---
   const double      alpha;               ///< Multiplier to momentum in #SGD optimization
   int               t;                   ///< Count of iterations
//---
   int               m_myIndex;           ///< Index of neuron in layer
   ENUM_ACTIVATION   activation;          ///< Activation type (#ENUM_ACTIVATION)
   ENUM_OPTIMIZATION optimization;        ///< Optimization method (#ENUM_OPTIMIZATION)
//---
///\ingroup neuron_base_ff
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL);               ///< \brief Feed Forward method of calling kernel ::FeedForward().@param NeuronOCL Pointer to previos layer.

///\ingroup neuron_base_opt
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL);        ///< Method for updating weights.\details Calling one of kernels ::UpdateWeightsMomentum() or ::UpdateWeightsAdam() in depends of optimization type (#ENUM_OPTIMIZATION).@param NeuronOCL Pointer to previos layer.

public:
   /** Constructor */CNeuronBaseOCL(void);
   /** Destructor */~CNeuronBaseOCL(void);
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint numNeurons, ENUM_OPTIMIZATION optimization_type);
   ///< Method of initialization class.@param[in] numOutputs Number of connections to next layer.@param[in] myIndex Index of neuron in layer.@param[in] open_cl Pointer to #COpenCLMy object. #param[in] numNeurons Number  of neurons in layer @param optimization_type Optimization type (#ENUM_OPTIMIZATION)@return Boolen result of operations.
   virtual void      SetActivationFunction(ENUM_ACTIVATION value) {  activation=value; }        ///< Set the type of activation function (#ENUM_ACTIVATION)
//---
   virtual int       getOutputIndex(void)          {  return Output.GetIndex();        }  ///< Get index of output buffer @return Index
   virtual int       getPrevOutIndex(void)         {  return PrevOutput.GetIndex();    }  ///< Get index of previous iteration output buffer @return Index
   virtual int       getGradientIndex(void)        {  return Gradient.GetIndex();      }  ///< Get index of gradient buffer @return Index
   virtual int       getWeightsIndex(void)         {  return Weights.GetIndex();       }  ///< Get index of weights matrix buffer @return Index
   virtual int       getDeltaWeightsIndex(void)    {  return DeltaWeights.GetIndex();  }  ///< Get index of delta weights matrix buffer (SGD)@return Index
   virtual int       getFirstMomentumIndex(void)   {  return FirstMomentum.GetIndex(); }  ///< Get index of first momentum matrix buffer (Adam)@return Index
   virtual int       getSecondMomentumIndex(void)  {  return SecondMomentum.GetIndex();}  ///< Get index of Second momentum matrix buffer (Adam)@return Index
//---
   virtual int       getOutputVal(double &values[])   {  return Output.GetData(values);      }  ///< Get values of output buffer @param[out] values Array of data @return number of items
   virtual int       getOutputVal(CArrayDouble *values)   {  return Output.GetData(values);  }  ///< Get values of output buffer @param[out] values Array of data @return number of items
   virtual int       getPrevVal(double &values[])     {  return PrevOutput.GetData(values);  }  ///< Get values of previous iteration output buffer @param[out] values Array of data @return number of items
   virtual int       getGradient(double &values[])    {  return Gradient.GetData(values);    }  ///< Get values of gradient buffer @param[out] values Array of data @return number of items
   virtual int       getWeights(double &values[])     {  return Weights.GetData(values);     }  ///< Get values of weights matrix buffer @param[out] values Array of data @return number of items
   virtual int       Neurons(void)                    {  return Output.Total();              }  ///< Get number of neurons in layer @return Number of neurons
   virtual int       Activation(void)                 {  return (int)activation;             }  ///< Get type of activation function @return Type (#ENUM_ACTIVATION)
   virtual int       getConnections(void)             {  return (CheckPointer(Weights)!=POINTER_INVALID ? Weights.Total()/(Gradient.Total()) : 0);   }   ///< Get number of connections 1 neuron to next layer @return Number of connections
//---
   virtual bool      FeedForward(CObject *SourceObject);                      ///< Dispatch method for defining the subroutine for feed forward process. @param SourceObject Pointer to the previous layer.
   virtual bool      calcHiddenGradients(CObject *TargetObject);              ///< Dispatch method for defining the subroutine for transferring the gradient to the previous layer. @param TargetObject Pointer to the next layer.
   virtual bool      UpdateInputWeights(CObject *SourceObject);               ///< Dispatch method for defining the subroutine for updating weights.@param SourceObject Pointer to previos layer.
///\ingroup neuron_base_gr
///@{
   virtual bool      calcHiddenGradients(CNeuronBaseOCL *NeuronOCL);          ///< Method to transfer gradient to previous layer by calling kernel ::CalcHiddenGradient(). @param NeuronOCL Pointer to next layer.
   virtual bool      calcOutputGradients(CArrayDouble *Target);               ///< Method of output gradients calculation by calling kernel ::CalcOutputGradient().@param Target target value
///@}
//---
   virtual bool      Save(int const file_handle);///< Save method @param[in] file_handle handle of file @return logical result of operation
   virtual bool      Load(int const file_handle);///< Load method @param[in] file_handle handle of file @return logical result of operation
   //---
   virtual int       Type(void)        const                      {  return defNeuronBaseOCL;                  }///< Identifier of class.@return Type of class
  };

为了完成代码的处理,我们创建一个封面。 "\mainpage" 命令用于标识封面块。 该命令后应加随封面标题。 在下面,我们添加项目描述,并创建参考列表。 列表项将用字符 "-" 标记。 为了创建指向早前创建的分组链接,则利用 “\ref” 命令。 当 Doxygen 生成文档时,将生成类层次结构(hierarchy.html)和所用文件(files.html)的页面。 将指向特定页面的链接添加到列表当中。 封面的最终代码如下所示。

///\mainpage NeuronNet
/// Library for creating Neural network for use in MQL5 experts.
/// - \ref const
/// - \ref enums
/// - \ref ObjectTypes
/// - \ref group1
/// - [<b>Class Hierarchy</b>](hierarchy.html)
/// - [<b>Files</b>](files.html)

以下页面是根据以上代码生成。


附件中提供了所有注释的完整代码。


5. 生成文档

代码处理完毕后,就进入下一个阶段。 在[第九篇]文章里详细介绍了 Doxygen 的安装和设置。 我们研究一些程序参数的设置。 首先,通知 Doxygen 它应该使用哪些文件:在“智能系统”选卡上的“输入”主题中,将必要的文件掩码添加到 FILE_PATTERNS 参数。 在这种情况下,我已添加了 "*.mqh" 和 "*.cl"。


现在,我们需要通知 Doxygen 如何解析添加的文件。 转到同一“智能系统”选卡上的“项目”主题,然后编辑 EXTENSION_MAPPING 参数,如下图所示。


为了让 Doxygen 能够生成数学公式,需激活 MathJax。 为此,激活“智能系统”选卡的 HTML 主题中的 USE_MATHJAX 参数,如下图所示。 


程序配置完毕后,转到“向导”选卡并指定项目名称,源文件的路径,以及显示生成文档的路径(所有这些步骤均在[第九篇]文章中展示过)。 转到“运行”选项卡,然后运行文档生成程序。

一旦程序完成,您将收到一个现成的文档。 Some screenshots are shown below. 附件中提供了完整的文档。



结束语

为所开发程序编制文档不是程序员的主要任务。 然而,在开发复杂项目时,此类文档至关重要。 它有助于跟踪任务的执行情况,协调开发团队的工作,并提供开发的整体视图。 共享知识时必须有文档。   

本文介绍了一种 MQL5 语言开发建档的机制。 它提供了该机制所有步骤的详细说明。 附件中提供了所做工作的结果,故每个人都可以对其进行评估。

希望我的经验对您有所帮助。

参考

  1. 神经网络变得轻松
  2. 神经网络变得轻松(第二部分):网络训练和测试
  3. 神经网络变得轻松(第三部分):卷积网络
  4. 神经网络变得轻松(第四部分):循环网络
  5. 神经网络变得轻松(第五部分):OpenCL 中的多线程计算
  6. 神经网络变得轻松(第六部分):神经网络学习率实验
  7. 神经网络变得轻松(第七部分):自适应优化方法
  8. 神经网络变得轻松(第八部分):关注机制
  9. 自动创建 MQL5 程序的文档
  10. Doxygen

本文中用到的程序

# 名称 类型 说明
1 NeuroNet.mqh 类库 用于创建神经网络的类库
2 NeuroNet.cl 代码库 OpenCL 程序代码库
3 html.zip  ZIP 存档 Doxygen 生成的文档存档 
4 NN.chm HTML 帮助 转换后的 HTML 帮助文件。 
5 Doxyfile   Doxygen 参数文件


分享到:
举报财经168客户端下载

全部回复

0/140

投稿 您想发表你的观点和看法?

更多人气分析师

  • 张亦巧

    人气2184文章4145粉丝45

    暂无个人简介信息

  • 梁孟梵

    人气2176文章3177粉丝39

    qq:2294906466 了解群指导添加微信mfmacd

  • 指导老师

    人气1864文章4423粉丝52

    暂无个人简介信息

  • 李冉晴

    人气2320文章3821粉丝34

    李冉晴,专业现贷实盘分析师。

  • 王启蒙现货黄金

    人气296文章3140粉丝8

    本人做分析师以来,并专注于贵金属投资市场,尤其是在现货黄金...

  • 张迎妤

    人气1896文章3305粉丝34

    个人专注于行情技术分析,消息面解读剖析,给予您第一时间方向...

  • 金泰铬J

    人气2328文章3925粉丝51

    投资问答解咨询金泰铬V/信tgtg67即可获取每日的实时资讯、行情...

  • 金算盘

    人气2696文章7761粉丝125

    高级分析师,混过名校,厮杀于股市和期货、证券市场多年,专注...

  • 金帝财神

    人气4760文章8329粉丝119

    本文由资深分析师金帝财神微信:934295330,指导黄金,白银,...

FX168财经

FX168财经学院

FX168财经

FX168北美