“EA 交易”运行期间平衡曲线斜率的控制

本文讲述的是,通过创建反馈来改善“EA 交易”性能的一种方法。本例中的反馈将基于平衡曲线斜率的测量。斜率的控制会通过调节交易量自动执行。“EA 交易”可行交易的模式如下:削减交易量、有效手数(根据最初调整)以及中间交易量。工作模式自动切换。

反馈链中采用了不同的调节特性:步进、带延迟的步进、线性。它允许将平衡曲线斜率控制系统调整为某特定系统特性。

主旨在于实现交易者决策制定过程的自动化,同时监测自己的交易系统。在不适宜的工作期间降低风险是合理的。返回正常工作模式后,风险又可以恢复到最初水平。

当然了,此系统并非什么万能灵药,也不会将一个亏损的“EA 交易”转变成一个盈利的“EA 交易”。某种程序上,这是“EA 交易” MM (资金管理)的一个补充,避免其在某个账户出现过大损失。

本文中包含一个库,它允许将此函数嵌入到任何“EA 交易”代码中。

操作原理

我们来深入了解一下控制平衡曲线斜率的系统操作原理。假设我们已有一个交易的“EA 交易”。则其假想的平衡曲线如下所示:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

图 1. 平衡曲线斜率控制系统的操作原理

交易操作使用恒定交易量的“EA 交易”的平衡初始曲线如上所示。已平仓交易则用红点标示。我们将这些点用一条曲线连接起来,从而展示出交易期间“EA 交易”的平衡变动(黑色粗线)。

现在,我们要持续追踪此线斜率与时间轴的角度(用蓝色细线表示)。或者更确切地说,根据某信号每个交易开盘之前,我们都会通过两个之前的平仓交易(为方便说明,或者是通过两个交易),计算坡度。如果坡度变得小于指定值,则我们的控制系统开始工作;它会根据计算得出的角度值和指定的调节函数减少交易量。

按这种方式,如果交易进入一个未成功的周期,则交易量会从 Vmax. 降至 Vmin. (Т3...Т5 交易周期)。过了 Т5 点之后,则以最小指定交易量执行交易 - 使用交易量拒绝模式。一旦“EA 交易”的盈利能力恢复、且平衡曲线的坡度升至指定值以上,则交易量开始增加。这发生于 Т8...Т10 区间。Т10 点之后,交易量操作恢复至初始状态 Vmax。

作为此类调节结果产生的平衡曲线,显示于图 1 下部。您可以看出,最初从 B1 到 B2 的减值已经减少,并变成从 B1 到 B2*。您还能注意到,利润在恢复最大交易量周期 Т8...Т10 内稍有降低 - 此为问题的另一面。

如以最小指定量执行交易,则平衡曲线的对应部分会被绿色高亮。黄色则表明该部分从最大到最小交易量及最小到最大交易量的转换。这里有多个可能的转换变量:

  • 步进 - 交易量以离散步进从最大到最小以及从最小到最大进行变换;

  • 线性 - 交易量根据调节区间内平衡曲线的坡度线性变化;

  • 带延迟的步进 - 从最大转换到最小交易量并返回,于不同坡度值处执行。

我们用图来说明:

编辑

添加图片注释,不超过 140 字(可选)

图 2. 调节特性的类型

调节特性会影响到控制系统的率值 - 启用/禁用延迟,从最大转换到最小交易量并返回的过程。建议基于试验选择达成最佳测试结果时的特性。

由此,我们利用基于平衡曲线坡度的反馈,来强化该交易系统。注意:这种交易量的调节仅适于未将交易量作为交易系统本身一部分的那些系统。比如说,如果采用的是马丁格尔 (Martingale) 原则,则不能在未改变初始“EA 交易”的情况下直接使用该系统。

此外,我们需要将注意力投向下述重点:

  • 平衡线斜率的管理效力,直接取决于正常操作模式下有效交易量同交易量拒绝模式下交易量的比率。该率值越大,管理效力越高。正因如此,初始有效交易量应明显大于最低可能交易量。

  • “EA 交易”平衡的涨落变更的平均周期,应远大于该控制系统的反应时间。否则,该系统就不能完成平衡曲线斜率的调节。反应时间的平均周期率值越大,系统效力就越大。此要求关系到每一个自动调节系统。

利用面向对象编程于 赫兹量化 中的实施

我们来编写一个实现上述方法的库。为此,我们要用到 MQL5 的一项新功能 - 面向对象方法。此方法允许您未来轻松开发和扩展我们的库,且无需从头重新编写大部分的代码。

TradeSymbol 类

因为已在新型 赫兹量化平台中实施了多货币测试,所以我们需要一个类,并将带有任何有效交易品种的整个操作封装其中。它允许在多货币“EA 交易”中使用此库。此类不会直接影响到控制系统,它是辅助性的。所以,此类会搭配有效交易品种一同操作。

 
 

//--------------------------------------------------------------------- // 工作交易品种的操作: //--------------------------------------------------------------------- class TradeSymbol { private: string trade_symbol; // 工作交易品种 private: double min_trade_volume; // 交易操作的最小交易量 double max_trade_volume; // 交易操作的最大交易量 double min_trade_volume_step; // 最小交易量增量 double max_total_volume; // 最大交易量增量 double symbol_point; // 点数大小 double symbol_tick_size; // 最小价格增量 int symbol_digits; // 小数点之后位数 protected: public: void RefreshSymbolInfo( ); // 刷新工作交易品种的市场情报 void SetTradeSymbol( string _symbol ); // 设置/改变 工作交易品种 string GetTradeSymbol( ); // 获取工作交易品种 double GetMaxTotalLots( ); // 获得最大累积量 double GetPoints( double _delta ); // 得到价格变化点数 public: double NormalizeLots( double _requied_lot ); // 得到归一化交易量 double NormalizePrice( double _org_price ); // 得到归一化的价格,同时考虑报价的逐步变化 public: void TradeSymbol( ); // 构造器 void ~TradeSymbol( ); // 析构器 };

该类的结构非常简单。目的就是按照某指定交易品种获取、存储并处理当前的市场信息。主要的方法为 TradeSymbol::RefreshSymbolInfo、TradeSymbol::NormalizeLots、 TradeSymbol::NormalizePrice。我们一个一个地来研究。

TradeSymbol::RefreshSymbolInfo 方法旨在按有效交易品种刷新市场信息。

 
 

//--------------------------------------------------------------------- // 刷新工作交易品种的市场信息: //--------------------------------------------------------------------- void TradeSymbol::RefreshSymbolInfo( ) { // 如果工作交易品种未设置, 不做任何事: if( GetTradeSymbol( ) == NULL ) { return; } // 计算归一化交易量的必要参数: min_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MIN ); max_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MAX ); min_trade_volume_step = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_STEP ); max_total_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_LIMIT ); symbol_point = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_POINT ); symbol_tick_size = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_TRADE_TICK_SIZE ); symbol_digits = ( int )SymbolInfoInteger( GetTradeSymbol( ), SYMBOL_DIGITS ); }

注意曾被多种方法用到的一个重要之处。由于当前赫兹量化的实现不允许使用带参数的构造函数,所以,您必须为有效交易品种的初始设置调用下述方法:

 
 

void SetTradeSymbol( string _symbol ); // 设置/改变 工作交易品种

TradeSymbol::NormalizeLots 方法用于获取一个正确的标准化交易量。我们知道,仓位不能小于经纪人所允许的最小可能值。仓位的最小变动幅度亦由经纪人确定,且可能有所区别。此方法会返回距底值最接近的交易量值。

它还会检查假定仓位的交易量是否超过了经纪人所允许的最大值。

 
 

//--------------------------------------------------------------------- // 获得标准化的交易量: //--------------------------------------------------------------------- // - 输入必要的交易量; // - 输出标准化交易量; //--------------------------------------------------------------------- double TradeSymbol::NormalizeLots( double _requied_lots ) { double lots, koeff; int nmbr; // 如果工作交易品种未设置, 不做任何事: if( GetTradeSymbol( ) == NULL ) { return( 0.0 ); } if( this.min_trade_volume_step > 0.0 ) { koeff = 1.0 / min_trade_volume_step; nmbr = ( int )MathLog10( koeff ); } else { koeff = 1.0 / min_trade_volume; nmbr = 2; } lots = MathFloor( _requied_lots * koeff ) / koeff; // 交易量下限: if( lots < min_trade_volume ) { lots = min_trade_volume; } // 交易量上限: if( lots > max_trade_volume ) { lots = max_trade_volume; } lots = NormalizeDouble( lots, nmbr ); return( lots ); }

TradeSymbol::NormalizePrice 方法用于获取正确的标准化价格。由于必须为给定的交易品种确定小数点后的有效位数(价格精度),我们需要删节价格。此外,有些交易品种(比如期货)的价格变化最小幅度大于一个点。因此,我们需要数倍于最小离散值的价格值。赫兹量化软件

 
 

//--------------------------------------------------------------------- // 考虑价格改变步骤的正常价格: //--------------------------------------------------------------------- double TradeSymbol::NormalizePrice( double _org_price ) { // 每步报价的最小点数: double min_price_step = NormalizeDouble( symbol_tick_size / symbol_point, 0 ); double norm_price = NormalizeDouble( NormalizeDouble(( NormalizeDouble( _org_price / symbol_point, 0 )) / min_price_step, 0 ) * min_price_step * symbol_point, symbol_digits ); return( norm_price ); }

必要的非标准化价格已输入该函数。它会返回最接近必要价格的标准化价格。

其它方法的目的详见相关注释,无需进一步讲解。

TBalanceHistory 类

此类旨在操作账户的余额历史,看名称就清楚了。它也是下述许多类的基类。此类的主要目的,就是访问“EA 交易”的交易历史。此外,您可以按有效交易品种、“幻数”、“EA 交易”监控的开始日期(或同时按上述所有三种元素)过滤历史。

 
 

//--------------------------------------------------------------------- // 结余历史的操作: //--------------------------------------------------------------------- class TBalanceHistory { private: long current_magic; // 当存取历史合约时的 "幻数" ( 0 - 任意数字 ) long current_type; // 合约类型 ( -1 - 全部 ) int current_limit_history; // 历史深度限制 ( 0 - 所有历史 ) datetime monitoring_begin_date; // 监视合约历史的开始时间 int real_trades; // 已执行的实际交易数量 protected: TradeSymbol trade_symbol; // 工作交易品种的操作 protected: // "原始" 数组: double org_datetime_array[ ]; // 交易日期时间 double org_result_array[ ]; // 交易结果 // 以时间归组的数据数组: double group_datetime_array[ ]; // 交易日期时间 double group_result_array[ ]; // 交易结果 double last_result_array[ ]; // 保存最后交易结果的数组 ( Y 轴上的点数 ) double last_datetime_array[ ]; // 存最后交易时间的数组 ( X 轴上的点数 ) private: void SortMasterSlaveArray( double& _m[ ], double& _s[ ] ); // 同步两个升序数组 public: void SetTradeSymbol( string _symbol ); // 设置/改变 工作交易品种 string GetTradeSymbol( ); // 获取工作交易品种 void RefreshSymbolInfo( ); // 刷新工作交易品种的市场情报 void SetMonitoringBeginDate( datetime _dt ); // 设置监视开始时间 datetime GetMonitoringBeginDate( ); // 获取监视开始时间 void SetFiltrParams( long _magic, long _type = -1, int _limit = 0 );// 设置合约过滤参数 public: // 得到最后交易结果: int GetTradeResultsArray( int _max_trades ); public: void TBalanceHistory( ); // 构造器 void ~TBalanceHistory( ); // 析构器 };

读取最新交易与历史结果的过滤设置,利用 TBalanceHistory::SetFiltrParams 方法来完成。它具有以下输入参数:

  • _magic - 应于历史中读取的交易“幻数”。如果指定零值,则带有任何“幻数”的交易都会被读取。

  • _type - 应被读取的交易类型。可拥有下述值 - DEAL_TYPE_BUY (仅限读取长线交易), DEAL_TYPE_SELL (仅限读取短线交易)以及 -1 (读取长线短线两种交易)。

  • _limit - 限制被分析交易历史的深度。如其等于零,则所有可用历史均被分析。

默认情况下,创建 TBalanceHistory 类的对象时,会设置下述值:_magic = 0, _type = -1, _limit = 0。

此类的主要方法为 TBalanceHistory::GetTradeResultsArray。该方法旨在利用最后交易的结果,来填充类成员数组 last_result_array 和 last_datetime_array。它具有以下输入参数:

  • _max_trades - 应由历史读取并写入输出数组的交易的最大数量。因为我们至少需要两个点才能计算坡度,所以该值不能少于 2。如果该值等于零,则对可用的整个交易历史进行分析。实际上,平衡曲线斜率计算所需点数是在此指定。赫兹量化软件

 
 

//--------------------------------------------------------------------- // 读取数组最近(按时间)交易的结果: //--------------------------------------------------------------------- // - 返回实际读取交易数量但不超过指定数量; //--------------------------------------------------------------------- int TBalanceHistory::GetTradeResultsArray( int _max_trades ) { int index, limit, count; long deal_type, deal_magic, deal_entry; datetime deal_close_time, current_time; ulong deal_ticket; // 合约的单号 double trade_result; string symbol, deal_symbol; real_trades = 0; // 交易数量不得少于 2: if( _max_trades < 2 ) { return( 0 ); } // 如果工作交易品种未指定, 不做任何事: symbol = trade_symbol.GetTradeSymbol( ); if( symbol == NULL ) { return( 0 ); } // 请求从指定时间至当前时刻的历史合约与订单: if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true ) { return( 0 ); } // 计算交易数量: count = HistoryDealsTotal( ); // 如果历史交易数量少于必要数量, 则退出: if( count < _max_trades ) { return( 0 ); } // 如果历史交易数量多于必要数量, 则限制它们: if( current_limit_history > 0 && count > current_limit_history ) { limit = count - current_limit_history; } else { limit = 0; } // 如果需要, 调整 "原始" 数组的维度至指定的交易数量: if(( ArraySize( org_datetime_array )) != ( count - limit )) { ArrayResize( org_datetime_array, count - limit ); ArrayResize( org_result_array, count - limit ); } // 以历史交易填充 "原始" 数组: real_trades = 0; for( index = count - 1; index >= limit; index-- ) { deal_ticket = HistoryDealGetTicket( index ); // 如果合约未平仓, 不要继续: deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY ); if( deal_entry != DEAL_ENTRY_OUT ) { continue; } // 检查合约的 "幻数",如果必要: deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC ); if( current_magic != 0 && deal_magic != current_magic ) { continue; } // 检查合约交易品种: deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL ); if( symbol != deal_symbol ) { continue; } // 检查合约类型,如果必要: deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE ); if( current_type != -1 && deal_type != current_type ) { continue; } else if( current_type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL )) { continue; } // 检查合约平仓时间: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; } // 所以, 我们可以读其它交易: org_datetime_array[ real_trades ] = deal_close_time / 60; org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME ); real_trades++; } // 如果此时少于必要交易数, 返回: if( real_trades < _max_trades ) { return( 0 ); } count = real_trades; // 以订单关闭时间对 "原始" 数组排序: SortMasterSlaveArray( org_datetime_array, org_result_array ); // 如果必要, 调整 group 数组的维度至指定的点数: if(( ArraySize( group_datetime_array )) != count ) { ArrayResize( group_datetime_array, count ); ArrayResize( group_result_array, count ); } ArrayInitialize( group_datetime_array, 0.0 ); ArrayInitialize( group_result_array, 0.0 ); // 以分组数据填充输出数组 ( 平仓时间为标识的分组 ): for( index = 0; index < count; index++ ) { // 得到其它交易: deal_close_time = ( datetime )org_datetime_array[ index ]; trade_result = org_result_array[ index ]; // 现在检查是否在输出数组中存在时间相同: current_time = ( datetime )group_datetime_array[ real_trades ]; if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 ) { real_trades++; // 移动指针至下一个元素 group_result_array[ real_trades ] = trade_result; group_datetime_array[ real_trades ] = deal_close_time; } else { group_result_array[ real_trades ] += trade_result; group_datetime_array[ real_trades ] = deal_close_time; } } real_trades++; // 现在是唯一元素的数量 // 如果此时少于必要交易数, 退出: if( real_trades < _max_trades ) { return( 0 ); } if( ArraySize( last_result_array ) != _max_trades ) { ArrayResize( last_result_array, _max_trades ); ArrayResize( last_datetime_array, _max_trades ); } // 以逆向索引写积累的数据到输出数组: for( index = 0; index < _max_trades; index++ ) { last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ]; last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ]; } // 在输出数组中,以积累的总和取代单个交易的结果: for( index = 1; index < _max_trades; index++ ) { last_result_array[ index ] += last_result_array[ index - 1 ]; } return( _max_trades ); }

开始时要执行强制性检查 - 是否已指定有效交易品种,以及输入参数是否正确。

然后,我们再读取从指定日期到当前时刻的交易与订单历史。利用下述部分代码实现:

 
 

// 请求从指定时间至当前时刻的历史合约与订单: if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true ) { return( 0 ); } // 计算交易数量: count = HistoryDealsTotal( ); // 如果历史交易数量少于必要数量, 则退出: if( count < _max_trades ) { return( 0 ); }

此外,还要检查历史交易的总数。如果少于指定数值,则没必要再执行进一步的行动了。“原始”数组一准备好,利用来自交易历史的信息进行填充的周期就马上执行。完成方式如下:

 
 

// 以历史交易填充 "原始" 数组: real_trades = 0; for( index = count - 1; index >= limit; index-- ) { deal_ticket = HistoryDealGetTicket( index ); // 如果交易未关闭, 不要继续: deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY ); if( deal_entry != DEAL_ENTRY_OUT ) { continue; } // 检查合约的 "幻数",如果必要: deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC ); if( _magic != 0 && deal_magic != _magic ) { continue; } // 检查合约交易品种: deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL ); if( symbol != deal_symbol ) { continue; } // 检查合约类型,如果必要: deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE ); if( _type != -1 && deal_type != _type ) { continue; } else if( _type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL )) { continue; } // 检查合约平仓时间: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; } // 所以, 我们可以读其它交易: org_datetime_array[ real_trades ] = deal_close_time / 60; org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME ); real_trades++; } // 如果此时少于必要交易数, 退出: if( real_trades < _max_trades ) { return( 0 ); }

开始时,利用 HistoryDealGetTicket 函数读取历史交易的价格跳动;而进一步的交易详情,则是利用获得的价格跳动来完成。因为我们只对已平仓交易感兴趣(我们要分析平衡),所以首先检查交易类型。为此要调用带有 DEAL_ENTRY 参数的 HistoryDealGetInteger 函数。如果此函数返回 DEAL_ENTRY_OUT,则其为某仓位的收盘。

继交易“幻数”之后检查的是交易的类型(即指定方法的输入参数)和交易的品种。如果交易的所有参数都符合要求,则检查最后一个参数 - 交易收盘的时间。完成方式如下:

 
 

// 检查合约平仓时间: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; }

将交易的日期/时间与给定的历史监测开始的日期/时间进行对比。如果交易的日期/时间大于给定的数据,则我们读取到数组的交易 - 读取按点数计的交易结果,以及按分钟计的交易时间(本例中是收盘时间)。此后,读取交易的计数器 real_trades 就会增长,且周期继续。

一旦“原始”数组已填充了必要的信息量,我们就应对存储交易收盘时间的数组进行排序。同时,我们还需要将对应的收盘时间保存于 org_datetime_array 数组,将交易结果保存于 org_result_array 数组。要利用专用的写入方法完成:

TBalanceHistory::SortMasterSlaveArray( double& _master[ ], double& _slave[ ] )。第一个参数是 _master - 按升序排列的一个数组。第二个参数为 _slave - 数组的元素应与第一个数组元素同时移动。排序则通过 "bubble" 方法来实现。

经过所有上述操作之后,我们拥有了两个带时间的数组以及按时间排序的交易结果。因为平衡曲线上对应每个时刻(X 轴上的点)的只有一个点(Y 轴上的点),所以,我们需要将带有同一收盘时间的数组元素(如果有的话)归组。利用下述部分代码执行此操作:

 
 

// 以分组数据填充输出数组 ( 平仓时间为标识的分组 ): real_trades = 0; for( index = 0; index < count; index++ ) { // 得到其它交易: deal_close_time = ( datetime )org_datetime_array[ index ]; trade_result = org_result_array[ index ]; // 现在检查是否在输出数组中存在时间相同: current_time = ( datetime )group_datetime_array[ real_trades ]; if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 ) { real_trades++; // 移动指针至下一个元素 group_result_array[ real_trades ] = trade_result; group_datetime_array[ real_trades ] = deal_close_time; } else { group_result_array[ real_trades ] += trade_result; group_datetime_array[ real_trades ] = deal_close_time; } } real_trades++; // 现在是唯一元素的数量

实际上,所有带有“相同”收盘时间的交易都被汇总于此。结果被写入到 赫兹量化软件TBalanceHistory::group_datetime_array (收盘时间)和 TBalanceHistory::group_result_array (交易结果)数组。此后,我们就得到了两个带有独特元素的排序数组。本例中的时间 ID 被认定为一分钟之内。该转变可以通过图形方式生动说明:

编辑

添加图片注释,不超过 140 字(可选)

图 3. 带相同时间的交易分组

一分钟之内的所有交易(图左侧),都被归入带时间舍入和结果汇总的一个组中。它允许平滑收盘交易的时间“颤振”,并提高调节的稳定性。

此后,您需要再完成所获数组的两次转变。反转元素的顺序,让最早的交易对应零元素;再用累积和(即余额)替换各次交易的结果。利用下述部分代码实现:

 
 

// 以逆向索引写积累的数据到输出数组: for( index = 0; index < _max_trades; index++ ) { last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ]; last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ]; } // 在输出数组中,以积累的总和取代单个交易的结果: for( index = 1; index < _max_trades; index++ ) { last_result_array[ index ] += last_result_array[ index - 1 ];

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值