下载MetaTrader 5

基于大众交易系统和交易机器人优化点金术的Expert Advisor(续)

17 三月 2016, 13:36
Nikolay Kositsin
0
463

简介

我收到上一篇文章的一位读者的提议,对回测过程稍加自动化,以实现同时获得所有优化结果的可能性。此外,手动改变测试周期不是很方便,该过程也应该自动化。这个想法非常棒。而且,MQL4 完全可以实现。所以,我将从问题的解决方法开始讲述。



回测自动化

要完成该任务我们需要:

1.在所需的 Expert Advisor 标头下面写入一行,内容如下:

//+==================================================================+
//| Custom BackTesting function                                      |
//+==================================================================+
#include <IsBackTestingTime.mqh>

使用该指令,将 IsBackTestingTime() 函数加入 EA 代码。不要忘记将 IsBackTestingTime.mqh 文件放入 INCLUDE 文件夹。该函数:

bool IsBackTestingTime()
 {
 } 

用于定义时间周期,在该时间内进行回测优化或回测。在该时间周期内,函数始终返回‘真’,在其他时间则返回‘假’。除了这个函数,通过以下指令将外部 EA 变量添加到 EA 代码:

//---- Declaration of external variables for backtesting
extern datetime Start_Time = D'2007.01.01'; // start time of zero optimization
extern int Opt_Period = 3; // optimization period in months, if less than zero, parameters are in days
extern int Test_Period = 2; // testing period in months
extern int Period_Shift = 1; // step of optimization period shift in months
extern int Opt_Number = 0; // optimization number

希望这些变量的含义对于读了我上一篇文章的人都很清楚,这里就不再解释。

2.在 EA 代码前的开始函数程序块设置调用 IsBackTestingTime() 函数的最简单的通用代码,根据回测优化的数量将 EA 运行限制在特定的时间范围。

 //----+ Execution of backtesting conditions
   if (!IsBackTestingTime())
                       return(0);

它的示意图如下:

//+==================================================================+
//|                                                 Exp_BackTest.mq4 |
//|                             Copyright © 2008,   Nikolay Kositsin | 
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+==================================================================+
#property copyright "Copyright © 2008, Nikolay Kositsin"
#property link "farria@mail.redcom.ru"
//+==================================================================+
//| Custom BackTesting function                                      |
//+==================================================================+
#include <IsBackTestingTime.mqh>
//---- INPUT PARAMETERS OF THE EA
//---- GLOBAL VARIABLES OF THE EA
//+==================================================================+
//| USER-DEFINED FUNCTIONS OF THE EA                                 |
//+==================================================================+
 
//+==================================================================+
//| Custom Expert initialization function                            |
//+==================================================================+
int init()
  {
//----+ +------------------------------------------------------------+    
//---- CODE FOR THE EA INITIALIZATION
//----+ +------------------------------------------------------------+                 
//---- end of initialization
   return(0);
  }
//+==================================================================+
//| Custom Expert iteration function                                 |
//+==================================================================+
int start()
  {
   //----+ Execution of backtesting conditions
   if (!IsBackTestingTime())
                       return(0);             
   //----+ +---------------------------------------------------------+    
   //----+ CODE OF THE EA ALGORITHM
   //----+ +---------------------------------------------------------+
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+

如果你对现成 EA 示例上的详细问题解决方案感兴趣,查看 EA 代码 Exp_5_1.mq4,源于上一篇文章中的 Exp_5.mq4,经修改后用于回测。实际上,与简单的 Expert Advisor 相比,这种 EA 的优化没有多大不同。但是,我认为除了 Opt_Number 变量外,回测变量应该进行优化,不过你可能有不同的看法。

重要的是记住:在测试优化后,我们得到的不是优化期间的结果,而是在其之后(右侧边界之外)的结果。使用遗传算法在一次运行内进行所有的回测优化并非最好的决定,在没有优化输入变量 Opt_Number 的情况下,单独深入分析每个回测优化更加有趣。

但即使在这种情况下,这种方法也促进了对 EA 行为的理解。应该记住,外部变量 Opt_Number 的值可以从零变化到某个最大值,该最大值可以使用以下方式定义:从执行所有回测优化的总周期(月数)减去回测优化周期的月数(Opt_Period)和减去回测周期(Test_Period)。获得的值加一。如果 Period_Shift 等于一,获得的结果将是 Opt_Number 变量的最大值。



基于两条移动线交叉的交易系统

这种交易系统的变体非常普遍。现在我们来分析奠定这种策略的算法。对于做多头寸,输入的算法如下:

对于做空头寸,则如下:

可以使用在指标中定义平均线的具有不同参数的两条相同移动线。假设定义 MovA 移动平均线的参数始终小于 MovB 移动平均线的同一参数。这样,在该交易系统中,MovA 是快速移动线,MovB 是慢速线。下面是基于两条 JMA 移动线的交易系统的实现变体:

//+==================================================================+
//|                                                        Exp_6.mq4 |
//|                             Copyright © 2007,   Nikolay Kositsin | 
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+==================================================================+
#property copyright "Copyright © 2007, Nikolay Kositsin"
#property link "farria@mail.redcom.ru"
//----+ +-------------------------------------------------------------------------------+
//---- EA INPUT PARAMETERS FOR BUY TRADES 
extern bool   Test_Up = true;//filter of trade calculations direction
extern int    Timeframe_Up = 240;
extern double Money_Management_Up = 0.1;
extern int    LengthA_Up = 4;  // smoothing depth of the quick moving
extern int    PhaseA_Up = 100; // parameter changing in the range 
          //-100 ... +100, influences the quality of transient process of quick moving
extern int    IPCA_Up = 0;/* Selecting prices, on which the indicator will be 
calculated by the quick moving (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int    LengthB_Up = 4; // smoothing depth increment of slow moving to quick one
extern int    PhaseB_Up = 100; // parameter changing in the range 
          //-100 ... +100, influences the quality of transient process of slow moving; 
extern int    IPCB_Up = 0;/* Selecting prices, on which the indicator will be 
calculated by the slow moving (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int    STOPLOSS_Up = 50;  // stop loss
extern int    TAKEPROFIT_Up = 100; // take profit
extern bool   ClosePos_Up = true; // forced position closing allowed
//----+ +-------------------------------------------------------------------------------+
//---- EA INPUT PARAMETERS FOR SELL TRADES 
extern bool   Test_Dn = true;//filter of trade calculations direction
extern int    Timeframe_Dn = 240;
extern double Money_Management_Dn = 0.1;
extern int    LengthA_Dn = 4;  // smoothing depth of the quick moving
extern int    PhaseA_Dn = 100; // parameter changing in the range
         // -100 ... +100, influences the quality of transient process of quick moving; 
extern int    IPCA_Dn = 0;/* Selecting prices, on which the indicator will be 
calculated by the quick moving (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int    LengthB_Dn = 4; // smoothing depth increment of slow moving to quick one
extern int    PhaseB_Dn = 100; // parameter changing in the range
         // -100 ... +100, influences the quality of transient process of slow moving; 
extern int    IPCB_Dn = 0;/* Selecting prices, on which the indicator will be 
calculated by the slow moving(0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int   STOPLOSS_Dn = 50;  // stop loss
extern int   TAKEPROFIT_Dn = 100; // take profit
extern bool   ClosePos_Dn = true; // forced position closing allowed
//----+ +-------------------------------------------------------------------------------+
//---- Integer variables for the minimum of calculation bars
int MinBar_Up, MinBar_Dn;
//+==================================================================+
//| Custom Expert functions                                          |
//+==================================================================+
#include <Lite_EXPERT1.mqh>
//+==================================================================+
//| Custom Expert initialization function                            |
//+==================================================================+
int init()
  {
//---- Checking the correctness of Timeframe_Up variable value
   if (Timeframe_Up != 1)
    if (Timeframe_Up != 5)
     if (Timeframe_Up != 15)
      if (Timeframe_Up != 30)
       if (Timeframe_Up != 60)
        if (Timeframe_Up != 240)
         if (Timeframe_Up != 1440)
           Print(StringConcatenate("Parameter Timeframe_Up cannot ",  
                                  "be equal to ", Timeframe_Up, "!!!"));
//---- Checking the correctness of Timeframe_Dn variable value 
   if (Timeframe_Dn != 1)
    if (Timeframe_Dn != 5)
     if (Timeframe_Dn != 15)
      if (Timeframe_Dn != 30)
       if (Timeframe_Dn != 60)
        if (Timeframe_Dn != 240)
         if (Timeframe_Dn != 1440)
           Print(StringConcatenate("Parameter Timeframe_Dn cannot ",  
                                 "be equal to ", Timeframe_Dn, "!!!")); 
//---- Initialization of variables            
   MinBar_Up = 4 + 30;
   MinBar_Dn = 4 + 30;                                        
//---- end of initialization
   return(0);
  }
//+==================================================================+
//| expert deinitialization function                                 |
//+==================================================================+  
int deinit()
  {
//----+
   
    //---- End of EA deinitialization
    return(0);
//----+ 
  }
//+==================================================================+
//| Custom Expert iteration function                                 |
//+==================================================================+
int start()
  {
   //----+ Declaring local variables
   int    bar;
   double MovA[2], MovB[2];
   //----+ Declaring static variables
   static int LastBars_Up, LastBars_Dn;
   static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop;
   
   //----++ CODE FOR LONG POSITIONS
   if (Test_Up)
    {
      int IBARS_Up = iBars(NULL, Timeframe_Up);
      
      if (IBARS_Up >= MinBar_Up)
       {
         if (LastBars_Up != IBARS_Up)
          {
           //----+ Initialization of variables 
           BUY_Sign = false;
           BUY_Stop = false;
           LastBars_Up = IBARS_Up;
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS        
           for(bar = 1; bar < 3; bar++)
                     MovA[bar - 1] =                  
                         iCustom(NULL, Timeframe_Up, 
                                "JJMA", LengthA_Up, PhaseA_Up, 
                                                   0, IPCA_Up, 0, bar);
           for(bar = 1; bar < 3; bar++)
                MovB[bar - 1] =                  
                   iCustom(NULL, Timeframe_Up, 
                     "JJMA", LengthA_Up + LengthB_Up, PhaseB_Up, 
                                                   0, IPCB_Up, 0, bar);
                                                   
           //----+ DEFINING SIGNALS FOR TRADES                                          
           if ( MovA[1] < MovB[1])
               if ( MovA[0] > MovB[0])
                        BUY_Sign = true;
                          
            if ( MovA[0] > MovB[0])
                        BUY_Stop = true;                                           
          }
          
          //----+ EXECUTION OF TRADES
          if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, 
                                          STOPLOSS_Up, TAKEPROFIT_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);
        }
     }
     
   //----++ CODE FOR SHORT POSITIONS
   if (Test_Dn)
    {
      int IBARS_Dn = iBars(NULL, Timeframe_Dn);
      
      if (IBARS_Dn >= MinBar_Dn)
       {
         if (LastBars_Dn != IBARS_Dn)
          {
           //----+ Initialization of variables 
           SELL_Sign = false;
           SELL_Stop = false;
           LastBars_Dn = IBARS_Dn; 
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS
           for(bar = 1; bar < 3; bar++)
                     MovA[bar - 1] =                  
                         iCustom(NULL, Timeframe_Dn, 
                                "JJMA", LengthA_Dn, PhaseA_Dn, 
                                                   0, IPCA_Dn, 0, bar);
           for(bar = 1; bar < 3; bar++)
                MovB[bar - 1] =                  
                   iCustom(NULL, Timeframe_Dn, 
                     "JJMA", LengthA_Dn + LengthB_Dn, PhaseB_Dn, 
                                                   0, IPCB_Dn, 0, bar);
           
           //----+ DEFINING SIGNALS FOR TRADES                                          
           if ( MovA[1] > MovB[1])
               if ( MovA[0] < MovB[0])
                        SELL_Sign = true;
                          
            if ( MovA[0] < MovB[0])
                        SELL_Stop = true;                                                
          }
          //----+ EXECUTION OF TRADES
          if (!OpenSellOrder1(SELL_Sign, 2, Money_Management_Dn, 
                                            STOPLOSS_Dn, TAKEPROFIT_Dn))
                                                                   return(-1);
          if (ClosePos_Dn)
                if (!CloseOrder1(SELL_Stop, 2))
                                        return(-1);
        }
     }
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+


基于两个震荡指标交叉的交易系统

这种交易策略不仅可以使用移动线,还可以使用震荡指标,但对于后者,根据上一篇文章所写,最好设置挂单,而不是在收到信号后立即进入市场。可以使用 NACD 图表作为生动的示例。

要设置 BuyLimit 挂单,算法如下:

下面是用于 SellLimit 类型订单的算法:

该 EA 的代码跟之前 EA 的代码类似:

//+==================================================================+
//|                                                        Exp_7.mq4 |
//|                             Copyright © 2007,   Nikolay Kositsin | 
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+==================================================================+
#property copyright "Copyright © 2007, Nikolay Kositsin"
#property link "farria@mail.redcom.ru"
//----+ +---------------------------------------------------------------------------+
//---- EA INPUT PARAMETERS FOR BUY TRADES 
extern bool   Test_Up = true; //filter of trade calculations direction
extern int    Timeframe_Up = 240;
extern double Money_Management_Up = 0.1;
extern int    FST_period_Up = 12;  // period of the quick moving
extern int    SLO_period_Up = 22; // period increment of slow moving to quick one
extern int    SIGN_period_Up = 8; // period of the signal line
extern int    Price_Up = 0;  // selecting prices, upon which MACD is calculated
extern int    STOPLOSS_Up = 50;  // stop loss
extern int    TAKEPROFIT_Up = 100; // take profit
extern int    PriceLevel_Up =40; // difference between the current price and 
                                         // price of pending order triggering
extern bool   ClosePos_Up = true; // forced position closing allowed
//----+ +---------------------------------------------------------------------------+
//---- EA INPUT PARAMETERS FOR SELL TRADES 
extern bool   Test_Dn = true; //filter of trade calculations direction
extern int    Timeframe_Dn = 240;
extern double Money_Management_Dn = 0.1;
extern int    FST_period_Dn = 12;  // period of the quick moving
extern int    SLO_period_Dn = 22; // period increment of slow moving to quick one
extern int    SIGN_period_Dn = 8; // period of the signal line
extern int    Price_Dn = 0;  // selecting prices, upon which MACD is calculated
extern int    STOPLOSS_Dn = 50;  // stop loss
extern int    TAKEPROFIT_Dn = 100; // take profit
extern int    PriceLevel_Dn =40;  // difference between the current price and 
                                         // price of pending order triggering
extern bool   ClosePos_Dn = true; // forced position closing allowed
//----+ +---------------------------------------------------------------------------+
//---- Integer variables for the minimum of calculation bars
int MinBar_Up, MinBar_Dn;
//+==================================================================+
//| Custom Expert functions                                          |
//+==================================================================+
#include <Lite_EXPERT1.mqh>
//+==================================================================+
//| Custom Expert initialization function                            |
//+==================================================================+
int init()
  {
//---- Checking the correctness of Timeframe_Up variable value
   if (Timeframe_Up != 1)
    if (Timeframe_Up != 5)
     if (Timeframe_Up != 15)
      if (Timeframe_Up != 30)
       if (Timeframe_Up != 60)
        if (Timeframe_Up != 240)
         if (Timeframe_Up != 1440)
           Print(StringConcatenate("Parameter Timeframe_Up cannot ",  
                                  "be equal to ", Timeframe_Up, "!!!"));
//---- Checking the correctness of Timeframe_Dn variable value 
   if (Timeframe_Dn != 1)
    if (Timeframe_Dn != 5)
     if (Timeframe_Dn != 15)
      if (Timeframe_Dn != 30)
       if (Timeframe_Dn != 60)
        if (Timeframe_Dn != 240)
         if (Timeframe_Dn != 1440)
           Print(StringConcatenate("Parameter Timeframe_Dn cannot ",  
                                 "be equal to ", Timeframe_Dn, "!!!")); 
//---- Initialization of variables             
   MinBar_Up = 4 + FST_period_Up + SLO_period_Up + SIGN_period_Up;
   MinBar_Dn = 4 + FST_period_Dn + SLO_period_Dn + SIGN_period_Dn;                                        
//---- end of initialization
   return(0);
  }
//+==================================================================+
//| expert deinitialization function                                 |
//+==================================================================+  
int deinit()
  {
//----+
   
    //---- End of the EA deinitialization
    return(0);
//----+ 
  }
//+==================================================================+
//| Custom Expert iteration function                                 |
//+==================================================================+
int start()
  {
   //----+ Declaring local variables
   int    bar;
   double MovA[2], MovB[2];
   //----+ Declaring static variables
   static int LastBars_Up, LastBars_Dn;
   static datetime StopTime_Up, StopTime_Dn; 
   static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop;
   
   //----++ CODE FOR LONG POSITIONS
   if (Test_Up)
    {
      int IBARS_Up = iBars(NULL, Timeframe_Up);
      
      if (IBARS_Up >= MinBar_Up)
       {
         if (LastBars_Up != IBARS_Up)
          {
           //----+ Initialization of variables 
           BUY_Sign = false;
           BUY_Stop = false;
           LastBars_Up = IBARS_Up;
           StopTime_Up = iTime(NULL, Timeframe_Up, 0)
                                            + 60 * Timeframe_Up;
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS           
           for(bar = 1; bar < 3; bar++)
             MovA[bar - 1] = iMACD(NULL, Timeframe_Up, 
                      FST_period_Up, FST_period_Up + SLO_period_Up,
                                         SIGN_period_Up, Price_Up, 0, bar);
                          
           for(bar = 1; bar < 3; bar++)
             MovB[bar - 1] = iMACD(NULL, Timeframe_Up, 
                      FST_period_Up, FST_period_Up + SLO_period_Up,
                                         SIGN_period_Up, Price_Up, 1, bar);
                 
           //----+ DEFINING SIGNALS FOR TRADES                                          
           if ( MovA[1] < MovB[1])
               if ( MovA[0] > MovB[0])
                        BUY_Sign = true;
                          
            if ( MovA[0] > MovB[0])
                        BUY_Stop = true;                                           
          }
          
          //----+ EXECUTION OF TRADES
          if (!OpenBuyLimitOrder1(BUY_Sign, 1, 
              Money_Management_Up, STOPLOSS_Up, TAKEPROFIT_Up,
                                            PriceLevel_Up, StopTime_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);
        }
     }
     
   //----++ CODE FOR SHORT POSITIONS
   if (Test_Dn)
    {
      int IBARS_Dn = iBars(NULL, Timeframe_Dn);
      
      if (IBARS_Dn >= MinBar_Dn)
       {
         if (LastBars_Dn != IBARS_Dn)
          {
           //----+ Initialization of variables 
           SELL_Sign = false;
           SELL_Stop = false;
           LastBars_Dn = IBARS_Dn;
           StopTime_Dn = iTime(NULL, Timeframe_Dn, 0) 
                                            + 60 * Timeframe_Dn; 
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS          
           for(bar = 1; bar < 3; bar++)
             MovA[bar - 1] = iMACD(NULL, Timeframe_Dn, 
                      FST_period_Dn, FST_period_Dn + SLO_period_Dn,
                                         SIGN_period_Dn, Price_Dn, 0, bar);
                          
           for(bar = 1; bar < 3; bar++)
             MovB[bar - 1] = iMACD(NULL, Timeframe_Dn, 
                      FST_period_Dn, FST_period_Dn + SLO_period_Dn,
                                         SIGN_period_Dn, Price_Dn, 1, bar);
           
           //----+ DEFINING SIGNALS FOR TRADES                                          
           if ( MovA[1] > MovB[1])
               if ( MovA[0] < MovB[0])
                        SELL_Sign = true;
                          
            if ( MovA[0] < MovB[0])
                        SELL_Stop = true;                                                
          }
          //----+ EXECUTION OF TRADES
          if (!OpenSellLimitOrder1(SELL_Sign, 2, 
              Money_Management_Dn, STOPLOSS_Dn, TAKEPROFIT_Dn,
                                            PriceLevel_Dn, StopTime_Dn))
                                                                 return(-1);
          if (ClosePos_Dn)
                if (!CloseOrder1(SELL_Stop, 2))
                                        return(-1);
        }
     }
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+


总结

另一篇文章也已完成。在 Expert Advisor 中基于绝对不同的指标变体实现了另一个交易系统。希望本文能够对 EA 编程新手有用,以进一步培养将正确形式化的算法转化为 Expert Advisor 可用和绝对有效的代码。


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/1521

附加的文件 |
EXPERTS.zip (8.58 KB)
INCLUDE.zip (17.88 KB)
indicators.zip (5.19 KB)
TESTER.zip (5.27 KB)
基于 MACD 扩展分析的交易助手 基于 MACD 扩展分析的交易助手

基于任意时间范围上实时交易中最后三个柱的 MACD 状态的扩展分析,&lsquo;交易助手&rsquo;脚本帮助你对打开头寸进行决策。也可以用于回测。

30 个指标和震荡指标的对比分析 30 个指标和震荡指标的对比分析

本文描述了可以对 30 个指标和震荡指标进行对比分析的 Expert Advisor,旨在形成有效的交易指标组。

Layman 的笔记:锯齿形调整浪... Layman 的笔记:锯齿形调整浪...

当然,每位交易新手在首次看到“神秘的”多段线后,无疑都渴望在接近极值点交易。真的如此简单。这是最大值。那是最小值。非常漂亮的历史图形。实践中如何呢?画了一条射线。它应该是——最高点!是卖出的时候了。现在价格下跌了。可恶!不!价格竟然开始上涨。哎!这只是没用的玩意,不是指标。于是将其扔掉!

通过脉动进行市场诊断 通过脉动进行市场诊断

本文尝试将特定市场及其时间段的强度可视化,以检测其规律性和行为模式。