MACD 样本 - MetaTrader 5 专家



MACD 样本 EA 包含在元交易者 5客户端,是使用 EA 进行交易的示例平滑异同移动平均线指标。
MACD Sample.mq5 Expert Advisor 的文件位于terminal_data_folder\MQL5\Experts\Examples\MACD\"。此 Expert Advisor 是 EA 开发中面向对象方法的示例。
让我们考虑一下 EA 交易的结构及其工作原理。
1 EA 属性
1.1. EA 属性
//+------------------------------------------------------------------+//| MACD 样本.mq5 | //| 版权所有 2009-2013,MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #财产版权 “版权所有 2009-2013,MetaQuotes Software Corp.” #财产关联 “https://www.mql5.com” #财产版本 “5.20” #财产描述 “确保专家与正常人一起工作非常重要” #财产描述 “图表和用户设置输入没有犯任何错误” #财产描述 “在我们的例子中是变量(手数、止盈、追踪止损),” #财产描述 “我们在超过 2*trend_period 条形图的图表上检查止盈”
前 5 行包含注释,接下来的 7 行使用预处理器指令设置 MQL5 程序的属性(版权、链接、版本、描述)#财产。
当您运行 EA 交易时,它们会显示在“公共”选项卡中:

图 1 MACD 示例 EA 的常用参数
1.2.包含文件
接下来,#包括指令告诉编译器包含包含以下内容的文件贸易类别标准库的。
//--- 包含文件 #包括<贸易\贸易.mqh>#包括<交易\SymbolInfo.mqh>#包括<交易\PositionInfo.mqh>#包括<交易\AccountInfo.mqh>
然后,适当类的实例用作 CExpert 类的成员变量(第 3 节)。
然后是类型、名称、默认值和注释。他们的作用如图所示。 2.
//--- EA 交易输入参数 输入 双倍的InpLots =0.1;// 很多 输入 整数 InpTakeProfit =50; // 止盈(以点为单位) 输入 整数 InpTrailingStop =30; // 追踪止损水平(以点为单位) 输入 整数 InpMACD开盘价位 =3; // MACD 开盘价(以点为单位) 输入 整数 InpMACD关闭级别=2; // MACD 收盘价(以点为单位) 输入 整数 InpMATrendPeriod =26; // MA 趋势周期
请注意,输入参数的名称具有前缀“Inp”。另请注意,全局变量以“Ext”为前缀。这种变量命名方法简化了许多不同变量的使用。
InpLots - 交易量、InpTakeProfit 和 InpTrailingStop 确定止盈和追踪止损水平,
输入参数行注释中的文本以及默认值显示在“选项”选项卡中,而不是输入参数的名称:

图 2. MACD 示例 EA 的输入参数
1.4.全局变量
然后声明全局变量ExtTimeOut。它将用于控制交易操作的执行时间。
整数外部超时=10;// 交易操作之间的时间(以秒为单位)
声明 CSampleExpert 类后,第 76 行声明了另一个全局变量:ExtExpert - CSampleExpert 类实例:
//--- ExtExpert 全局变量CSampleExpert ExtExpert;
ExtExpert 对象(CSampleExpert 类示例)包含交易策略的基本逻辑(第 3 节)。
2. 事件处理函数
事件处理函数
2.1. OnInit()初始化函数
这OnInit()函数在 EA 交易首次启动期间调用一次。通常在 OnInit() 事件处理程序中,EA 准备运行:检查输入参数、初始化指标和参数等。如果出现严重错误,当进一步的工作没有意义时,函数将退出并返回代码 INIT_FAILED。
//+------------------------------------------------------------------+ //|专家初始化函数 | //+------------------------------------------------------------------+ 整数 初始化时(空白) {//--- 初始化和创建所有必需的对象 如果(!ExtExpert.Init()) 返回(初始化失败);//--- 成功初始化 返回(初始化成功); }
在这种情况下,调用 ExtExpert 对象的 Init() 方法,该方法根据操作所需的所有对象的准备情况返回 true 或 false(参见第 3.4 节)。如果发生错误,OnInit() 将退出并返回代码初始化失败- 这是在初始化不成功的情况下完成 EA/指标操作的正确方法。
2.2. OnTick() 函数
这OnTick()每次收到运行 EA 的图表品种的新报价时,都会调用该函数。
//+------------------------------------------------------------------+ //|专家新蜱处理功能 | //+------------------------------------------------------------------+ 空白 勾选(空白) { 静止的 日期时间限制时间=0;// 存储最后一次调用时间+超时时间 //--- 如果未经过所需时间,则不进行操作 如果(时间当前()>=限制时间) { //--- 检查数据 如果(酒吧(象征(),时期())>2*InpMATrendPeriod) { //---调用Processing()方法之后将 limit_time 的值增加 ExtTimeOut 如果(ExtExpert.Processing()) 限制时间=时间当前()+ExtTimeOut; } } }
OnTick() 事件处理程序中包含定期调用 ExtExpert.Processing() 方法的机制,用于在满足交易条件时进行市场分析和交易操作。
调用之间的时间间隔由 ExtTimeOut 输入参数的值设置。
2.3. OnDeInit() 反初始化函数
OnDeInit()当 EA 从图表中删除时调用。如果程序在运行期间放置图形对象,则可以将它们从图表中删除。
在这些示例中,没有使用去初始化函数,也没有执行任何操作。
3.CSampleExpert类
3.1. CSampleExpert 类
//+------------------------------------------------------------------+ //| MACD 示例 EA 类示例 | //+------------------------------------------------------------------+ 班级CSampleExpert {受保护的://--- 受保护的变量 - 类成员仅在类方法内部可用 双倍的 m_调整_点; // 3/5 位报价的乘数 CTrade m_trade; // CTrade 类示例 CSymbolInfo m_symbol; // CSymbolInfo 类示例 CPositionInfo m_position; // CPositionInfo 类示例 CAccountInfo m_account; // CAccountInfo 类示例 //--- 指标句柄 整数 m_handle_macd; // MACD 指标句柄 整数 m_handle_ema; // 移动平均线指标句柄 //--- 指标缓冲区 双倍的 m_buff_MACD_main[]; // MACD 指标主线缓冲区 双倍的 m_buff_MACD_signal[]; // MACD 指标信号线的缓冲区 双倍的 m_buff_EMA[]; // EMA 指标缓冲区 //--- 当前指标值 双倍的 m_macd_当前; 双倍的 m_macd_上一个; 双倍的 m_signal_current; 双倍的 m_signal_previous; 双倍的 m_ema_当前; 双倍的 m_ema_上一个; //--- 级别(以标准点为单位) 双倍的 m_macd_open_level; 双倍的 m_macd_close_level; 双倍的 m_traling_stop; 双倍的 m_take_profit;民众: //--- 构造函数 CSampleExpert(空白); //--- 析构函数 ~CSampleExpert(空白);//--- 公共方法可以从类外部调用 //--- 初始化方法 布尔值 初始化(空白); //--- 去初始化方法 空白 去初始化(空白); //--- 处理方法 布尔值 加工(空白);受保护的://--- 受保护的方法只能在类方法内部访问 布尔值 初始化检查参数(常量 整数数字_调整); 布尔值 初始化指标(空白); 布尔值 长闭(空白); 布尔值 空头平仓(空白); 布尔值 长修改(空白); 布尔值 短修改(空白); 布尔值 长开(空白); 布尔值 空头开仓(空白); };
EA 类包含变量(类成员)和函数(类方法)的声明。
为了更方便地使用变量,所有类成员变量都包含前缀“m_”(成员),这表明变量是类成员。在声明变量或方法之前,指定其类型(或函数的返回值)。
类成员变量和方法的可见性是使用定义的访问修饰符。在 CSampleExpert 类中,使用了 protected 和 public 修饰符。公共部分中定义的所有变量和方法都是公共的,可以从外部访问。 CSampleExpert 类有五个这样的方法:
使用 protected 访问修饰符声明的 CSampleExpert 类成员变量仅在 CSampleExpert 类方法(和子类)内部可用。
使用 protected 访问修饰符声明的 CSampleExpert 类方法:
3.2. CSampleExpert 类构造函数
//+------------------------------------------------------------------+ //| CSampleExpert 类构造函数 | //+------------------------------------------------------------------+CSampleExpert::CSampleExpert(空白) : m_调整点(0), m_handle_macd(INVALID_HANDLE), m_handle_ema(INVALID_HANDLE), m_macd_当前(0), m_macd_上一个(0), m_信号_电流(0), m_signal_previous(0), m_ema_当前(0), m_ema_上一个(0), m_macd_open_level(0), m_macd_close_level(0), m_traling_stop(0), m_take_profit(0) { 数组集为系列(m_buff_MACD_main,真的); 数组集为系列(m_buff_MACD_信号,真的); 数组集为系列(m_buff_EMA,真的); }
类构造函数创建类示例对象时自动调用。调用时,会设置类成员变量的默认值(在括号中)和时间序列分度方向为 m_buff_MACD_main[]、m_buff_MACD_signal[]、m_buff_EMA[] 设置。
3.3. CSampleExpert 类析构函数
//+------------------------------------------------------------------+ //| CSampleExpert 类析构函数 | //+------------------------------------------------------------------+CSampleExpert::~CSampleExpert(空白) { }
CSampleExpert 类析构函数不包含任何代码。
3.4. CSampleExpert 类的 Init 方法
//+------------------------------------------------------------------+ //|输入参数的初始化和验证 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::初始化(空白) {//--- 设置公共属性 m_symbol. 名称(象征()); // 象征 m_trade.SetExpertMagicNumber(12345); // 魔法 //--- 考虑 3/5 位报价 整数数字_调整=1; 如果(m_符号。数字()==3|| m_符号。数字()==5) 数字_调整=10; m_调整_点=m_符号。观点()*数字调整;//--- 设置级别值时考虑 m_adjusted_point 修饰符 m_macd_open_level =InpMACDOpenLevel*m_adjusted_point; m_macd_close_level=InpMACDCloseLevel*m_adjusted_point; m_traling_stop =InpTrailingStop*m_adjusted_point; m_take_profit =InpTakeProfit*m_adjusted_point;//--- 设置 3 点的滑点 m_trade.SetDeviationInPoints(3*数字调整);//--- 如果(!InitCheckParameters(digits_adjust)) 返回(错误的); 如果(!InitIndicators()) 返回(错误的);//--- 成功完成 返回(真的); }
在Init()方法中,初始化类成员变量并验证输入参数。
的召唤姓名()m_symbol 对象的方法(符号信息类实例)设置运行 EA 交易的交易品种的名称,然后是方法设置ExpertMagicNumber()被称为;它为 m_trade 对象设置 EA 幻数的值(将用于交易操作)。之后,数字()方法用于请求符号小数点后的位数,并且如有必要,对级别值进行更正。
接下来是设置点偏差()调用 m_trade 对象的方法,在该方法中设置交易操作中允许滑点的值。
3.5. CSampleExpert 类的 InitCheckParameters 方法
//+------------------------------------------------------------------+ //|检查输入参数 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::InitCheckParameters(常量 整数数字_调整) {//--- 检查止盈水平的正确性 如果(InpTakeProfit*digits_adjust打印函数(“止盈必须大于 %d”,m_symbol.StopsLevel()); 返回(错误的); }//--- 检查追踪止损水平的正确性 如果(InpTrailingStop*digits_adjust 打印函数(“追踪止损必须大于 %d”,m_symbol.StopsLevel()); 返回(错误的); }//--- 检查交易量的正确性 如果(InpLots m_symbol.LotsMax()) { 打印函数(“手数必须在 %f 到 %f 范围内”,m_symbol.LotsMin(),m_symbol.LotsMax()); 返回(错误的); } 如果(数学抗体(InpLots/m_symbol.LotsStep()-数学轮(InpLots/m_symbol.LotsStep()))>1.0E-10) { 打印函数(“手数与手数步骤 %f 不对应”,m_symbol.LotsStep()); 返回(错误的); }//--- 如果止盈<=追踪止损则显示警告 如果(InpTakeProfit<=InpTrailingStop) 打印函数(“警告:追踪止损必须小于止盈”);//--- 成功完成 返回(真的); }
在 InitCheckParameters() 方法中检查 EA 输入参数的正确性。如果任何参数无效,则会显示相应的消息,并且函数返回 false。
3.6. CSampleExpert 类的 InitIndicators() 方法
//+------------------------------------------------------------------+ //|指标初始化方法 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::InitIndicators(空白) {//--- 创建 MACD 指标 如果(m_handle_macd==INVALID_HANDLE) 如果((m_handle_macd=平滑异同移动平均线(无效的,0,12,26,9,PRICE_CLOSE))==INVALID_HANDLE) { 打印函数(“创建 MACD 指标时出错”); 返回(错误的); }//--- 创建 EMA 指标 如果(m_handle_ema==INVALID_HANDLE) 如果((m_handle_ema=伊玛(无效的,0,InpMATrendPeriod,0,模式_EMA,PRICE_CLOSE))==INVALID_HANDLE) { 打印函数(“创建 EMA 指标时出错”); 返回(错误的); }//--- 成功完成 返回(真的); }
在 InitIndicators() 方法中,检查 m_handle_macd 和 m_handle_ema 变量初始值的正确性(它们必须等于 INVALID_HANDLE,因为它们已在构造函数中初始化),以及技术指标平滑异同移动平均线和移动平均线创建(使用平滑异同移动平均线和伊玛功能)。如果成功,该函数将返回 true,并且指标句柄将保存在 m_handle_macd 和 m_handle_ema 类成员中。
创建的指标的句柄将用于检查计算的数据量(计算的条数)并获得数值(复制缓冲区) 中的Processing() 方法中的指标。
3.7. CSampleExpert 类的 LongClosed() 方法
//+------------------------------------------------------------------+ //|检查平仓条件 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::LongClosed(空白) { 布尔值分辨率=错误的;//--- 是否应该平仓? 如果(m_macd_当前>0) 如果(m_macd_currentm_signal_previous) 如果(m_macd_current>m_macd_close_level) { //--- 平仓 如果(m_trade.PositionClose(象征())) 打印函数(“%s 的多头仓位将被平仓”,象征()); 别的 打印函数(“%s 平仓时出错:‘%s’”,象征(),m_trade.ResultComment()); 分辨率=真的; }//--- 返回结果 返回(研究); }
如果满足平仓条件,LongClosed() 方法将返回 true(并平仓多头头寸):
3.8. CSampleExpert 类的 ShortClosed() 方法
//+------------------------------------------------------------------+ //|检查空头平仓条件 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::ShortClosed(空白) { 布尔值分辨率=错误的;//--- 是否应该平仓? 如果(m_macd_当前<0) 如果(m_macd_current>m_signal_current && m_macd_previous如果(数学抗体(m_macd_current)>m_macd_close_level) { //--- 平仓 如果(m_trade.PositionClose(象征())) 打印函数(“%s 的空头头寸将被平仓”,象征()); 别的 打印函数(“%s 平仓时出错:‘%s’”,象征(),m_trade.ResultComment()); 分辨率=真的; }//--- 返回结果 返回(研究); }
如果满足平仓空头仓位,则 ShortClosed() 方法返回 true(并平仓空头仓位):
3.9. CSampleExpert 类的 LongModified() 方法
//+------------------------------------------------------------------+ //|检查多头头寸修改的条件 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::LongModified(空白) { 布尔值分辨率=错误的;//--- 检查是否需要追踪止损 如果(InpTrailingStop>0) { 如果(m_symbol.Bid()-m_position.PriceOpen()>m_adjusted_point*InpTrailingStop) { 双倍的斯尔=规范化双精度(m_symbol.Bid()-m_traling_stop,m_symbol.数字()); 双倍的tp=m_position.TakeProfit(); 如果(m_position.StopLoss()0.0) { //--- 修改仓位的止损和获利 如果(m_trade.PositionModify(象征(),sl,tp)) 打印函数(“%s 的多头仓位待修改”,象征()); 别的 { 打印函数(“%s 修改位置时出错:‘%s’”,象征(),m_trade.ResultComment()); 打印函数(“修改参数:SL=%f,TP=%f”,sl,tp); } 分辨率=真的; } } }//--- 返回结果 返回(研究); }
如果满足多头仓位修改条件,LongModified() 方法返回 true(并修改仓位的止损值): 如果输入 InpTrailingStop 的值>0,则检查价格在持仓方向上从开盘价经过 InpTrailingStop 点的情况。接下来,计算新止损水平的值并修改未平仓头寸的止损参数。
3.10. CSampleExpert 类的 ShortModified 方法
//+------------------------------------------------------------------+ //|检查多头头寸修改的条件 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::ShortModified(空白) { 布尔值 分辨率=错误的;//--- 检查是否需要追踪止损 如果(InpTrailingStop>0) { 如果((m_position.PriceOpen()-m_symbol.Ask())>(m_adjusted_point*InpTrailingStop)) { 双倍的斯尔=规范化双精度(m_symbol.Ask()+m_traling_stop,m_symbol.数字()); 双倍的tp=m_position.TakeProfit(); 如果(m_position.StopLoss()>sl || m_position.StopLoss()==0.0) { //--- 修改仓位的止损和获利 如果(m_trade.PositionModify(象征(),sl,tp)) 打印函数(“%s 的空头仓位待修改”,象征()); 别的 { 打印函数(“%s 修改位置时出错:‘%s’”,象征(),m_trade.ResultComment()); 打印函数(“修改参数:SL=%f,TP=%f”,sl,tp); } 分辨率=真的; } } }//--- 返回结果 返回(研究); }
如果满足空头仓位修改条件,ShortModified() 方法将返回 true(并修改仓位的止损值): 如果输入 InpTrailingStop 的值>0,则检查价格在持仓方向上从开盘价经过 InpTrailingStop 点的情况。接下来,计算新止损水平的值并修改未平仓头寸的止损参数。
3.11. CSampleExpert 类的 LongOpened() 方法
//+------------------------------------------------------------------+ //|检查多头开仓 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::LongOpened(空白) { 布尔值分辨率=错误的;//--- 检查开立多头头寸的条件 如果(m_macd_当前<0) 如果(m_macd_current>m_signal_current && m_macd_previous如果(数学抗体(m_macd_current)>(m_macd_open_level) && m_ema_current>m_ema_previous) { 双倍的价格=m_symbol.Ask(); 双倍的tp =m_symbol.Bid()+m_take_profit; //--- 检查可用保证金 如果(m_account.FreeMarginCheck(象征(),订单类型购买,InpLots,价格)<0.0) 打印函数(“我们没有钱。可用保证金 = %f”,m_account.FreeMargin()); 别的 { //--- 开立多头头寸 如果(m_trade.PositionOpen(象征(),订单类型购买,InpLots,价格,0.0,tp)) 打印函数(“%s 已开仓”,象征()); 别的 { 打印函数(“%s 建立买入仓位时出错:‘%s’”,象征(),m_trade.ResultComment()); 打印函数(“开放参数:价格=%f,TP=%f”,价格,tp); } } 分辨率=真的; }//--- 返回结果 返回(研究); }
如果满足开仓条件,LongOpened() 方法将返回 true(并开立多头头寸):
当满足所有条件时,将检查可用保证金(可用保证金检查()的方法账户信息标准库类),并使用以下命令开立多头头寸持仓开仓()的方法贸易网班级。
3.12. CSampleExpert 类的 ShortOpened 方法
//+------------------------------------------------------------------+ //|检查空头仓位 | //+------------------------------------------------------------------+ 布尔值CSampleExpert::ShortOpened(空白) { 布尔值分辨率=错误的;//--- 检查开空头寸的条件 如果(m_macd_当前>0) 如果(m_macd_currentm_signal_previous) 如果(m_macd_current>(m_macd_open_level) && m_ema_current 双倍的价格=m_symbol.Bid(); 双倍的tp =m_symbol.Ask()-m_take_profit; //--- 检查可用保证金 如果(m_account.FreeMarginCheck(象征(),订单类型_卖出,InpLots,价格)<0.0) 打印函数(“我们没有钱。可用保证金 = %f”,m_account.FreeMargin()); 别的 { //--- 开空头寸 如果(m_trade.PositionOpen(象征(),订单类型_卖出,InpLots,价格,0.0,tp)) 打印函数(“%s 已开仓”,象征()); 别的 { 打印函数(“%s 建仓卖出仓位时出错:‘%s’”,象征(),m_trade.ResultComment()); 打印函数(“开放参数:价格=%f,TP=%f”,价格,tp); } } 分辨率=真的; }//--- 返回结果 返回(研究); }
如果满足开仓条件,ShortOpened() 方法将返回 true(并开立空头头寸):
当满足所有条件时,将检查可用保证金(可用保证金检查()的方法账户信息标准库类)并使用以下命令打开空头头寸持仓开仓()的方法贸易网班级。
3.13 CSampleExpert类的Processing()方法
//+------------------------------------------------------------------+ //|如果处理了任何位置,则 main 函数返回 true | //+------------------------------------------------------------------+ 布尔值CSampleExpert::处理(空白) {//--- 更新报价 如果(!m_symbol.RefreshRates()) 返回(错误的);//--- 更新指标值 如果(计算的条数(m_handle_macd)<2||计算的条数(m_handle_ema)<2) 返回(错误的); 如果(复制缓冲区(m_handle_macd,0,0,2,m_buff_MACD_main) !=2|| 复制缓冲区(m_handle_macd,1,0,2,m_buff_MACD_信号)!=2|| 复制缓冲区(m_handle_ema,0,0,2,m_buff_EMA) !=2) 返回(错误的);//--- 简化指标工作并加快访问速度 //--- 指标的当前值保存在内部变量(类成员)中 m_macd_current =m_buff_MACD_main[0]; m_macd_previous =m_buff_MACD_main[1]; m_signal_current =m_buff_MACD_signal[0]; m_signal_previous=m_buff_MACD_signal[1]; m_ema_current =m_buff_EMA[0]; m_ema_previous =m_buff_EMA[1];//--- 正确的市场进入很重要,但正确的退出更为重要 //--- 首先检查是否有未平仓合约 如果(m_position.选择(象征())) { 如果(m_position.PositionType()==POSITION_TYPE_BUY) { //--- 如有必要,我们尝试平仓或修改多头头寸 如果(长闭()) 返回(真的); 如果(长修改()) 返回(真的); } 别的 { //--- 如有必要,我们尝试平仓或修改空头头寸 如果(短闭()) 返回(真的); 如果(短修改()) 返回(真的); } }//--- 没有未平仓头寸 别的 { //--- 检查条件并在必要时开立多头头寸 如果(长开()) 返回(真的); //--- 检查条件并在必要时开立空头头寸 如果(短开()) 返回(真的); }//--- 退出而不进行位置处理 返回(错误的); }
CSampleExpert 类的Processing() 方法是EA 交易的方法。在OnTick()事件处理程序中调用Processing()方法,并监视连续调用该方法之间的时间间隔(不少于ExtTimeOut秒)(第2.2节)。
通过致电刷新率()的方法符号信息班级报价已更新。这条形计算()函数用于请求柱的数量,其指标平滑异同移动平均线和移动平均线计算(第 3.6 节);如果柱数小于 2,则退出该函数并返回 false。
接下来,复制缓冲区函数调用请求技术指标的最后两个值(主要和信号 MACD 线以及移动平均线值);如果复制的数据量小于2,则退出该函数。之后,数组 m_buff_MACD_main[]、m_buff_MACD_signal[] 和 m_buff_EMA[] 中的指标值被复制到变量 m_macd_current、m_macd_previous、m_signal_current、m_signal_previous、m_ema_current 和 m_ema_previous。
下一步是通过班级进行职位工作C位置信息标准库的。如果选择()方法调用已返回 true,这意味着当前有一个未平仓头寸,其类型由使用位置类型()方法。进一步的工作将根据未平仓头寸的类型进行。
可以使用以下方法找到参数的最佳值策略测试仪MetaTrader 5 终端的。
图 3 显示了 2013 年使用默认设置的 EA 交易测试结果。

图 3. MACD 样本 EA 交易的回测结果
MACD 样本 Expert Advisor,包含在标准交付包中元交易者 5终端,是 EA 开发中面向对象方法的一个示例。
附件下载
📎 macd_sample.mq5 (17.98 KB)
Source: MQL5 #2154
💡 精彩内容推荐
✍️ 楼主最新发布
- •
- •
- •
- •
- •
- •
🔗 您可能感兴趣
- •
- •
- •
- •
- •
- •
