100 个最佳优化递次(第 1 部分)。 开发优化分析器·进阶篇
📊

100 个最佳优化递次(第 1 部分)。 开发优化分析器·进阶篇

(2/3)· 家用电脑跑优化动辄数天,递次结果却只剩几张图?这篇拆开分析器怎么存、怎么算、怎么看

案例拆解新手友好 第 2/3 篇
把 EA 优化跑完就直接挑排名第一的递次,是很多人的习惯动作。可横盘策略在趋势段亏、前瞻测试被误读成连续亏损,这类坑靠默认报告根本看不出来。等真金白银挂上实盘,才发现参数只是表面漂亮。

◍ 回测面板的总控入口怎么搭

要在 MT5 里做一套带图形界面的回测分析面板,第一步是把所有界面元素一次性挂到窗口管理器上。下面这组函数声明覆盖了标签、按钮、下拉框、日历、文本框、表格、单选、分隔线、图表和勾选框共 11 类控件,少一个都可能导致后续事件分发时界面空白。 事件路由集中在 OnEvent 里,它把 30 余个交互回调(如 Btn_Update_Click、RealPL_pressed、FriquencyChart_click)全部转发给对应的处理函数。实测中若漏写其中一行,对应按钮点击后无任何反应,且编译器不会报错,只能靠逐个核对来排错。 初始化阶段用 CWindowManager 实例配合 CPresenter presenter(&_window) 完成 GUI 构建,OnInit 中调用 _window.CreateGUI() 返回 false 即代表创建失败。外汇与贵金属回测涉及高杠杆与滑点,面板数据仅作概率参考,不代表实盘倾向。

MQL5 / C++
class=class="str">"cmt">//===============================================================================
class=class="str">"cmt">//--- 所有标签
  class="type">bool                Create_all_lables();
  class="type">bool                Create_all_buttons();
  class="type">bool                Create_all_comboBoxies();
  class="type">bool                Create_all_dropCalendars();
  class="type">bool                Create_all_textEdits();
  class="type">bool                Create_all_textBoxies();
  class="type">bool                Create_all_tables();
  class="type">bool                Create_all_radioButtons();
  class="type">bool                Create_all_SepLines();
  class="type">bool                Create_all_Charts();
  class="type">bool                Create_all_CheckBoxies();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Btn_Update_Click                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CWindowManager::Btn_Update_Click(class="kw">const class="type">int id,class="kw">const class="type">long &lparam)
  {
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==Btn_update.Id())
     {
      presenter.Btn_Update_Click();
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnEvent                                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CWindowManager::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam)
  {
   Btn_Update_Click(id,lparam);
   Btn_Load_Click(id,lparam);
   OptimisationData_inMainTable_selected(id,lparam);
   OptimisationData_inResults_selected(id,lparam);
   Update_PLByDays(id,lparam);
   RealPL_pressed(id,lparam);
   OneLotPL_pressed(id,lparam);
   CoverPL_pressed(id,lparam);
   RealPL_pressed_2(id,lparam);
   OneLotPL_pressed_2(id,lparam);
   RealPL_pressed_4(id,lparam);
   OneLotPL_pressed_4(id,lparam);
   SelectHistogrameType(id,lparam);
   SaveToFile_Click(id,lparam);
   Deals_passed(id,lparam);
   BuyAndHold_passed(id,lparam);
   Optimisation_passed(id,lparam);
   OptimisationParam_selected(id,lparam);
   isCover_clicked(id,lparam);
   ChartFlag(id,lparam);
   show_FriquencyChart(id,lparam);
   FriquencyChart_click(id,lparam);
   Filtre_click(id,lparam);
   Reset_click(id,lparam);
   RealPL_pressed_3(id,lparam);
   OneLotPL_pressed_3(id,lparam);
   ShowAll_Click(id,lparam);
   DaySelect(id,lparam);
  }
class="macro">#include "Presenter.mqh"
CWindowManager _window;
CPresenter Presenter(&_window);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能系统初始化函数                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   if(!_window.CreateGUI())
     {

「EA 初始化失败与图表事件转发」

在 MT5 的 EA 骨架里,初始化函数必须给终端一个明确的交代:界面没建起来就返回 INIT_FAILED,建好了才返回 INIT_SUCCEEDED。上面这段里,如果图形接口创建失败,会先打印出函数名加提示串,再直接 return(INIT_FAILED),此时 EA 不会挂载运行,避免后续空指针崩在图表上。 OnDeinit 只做一件事:把终端下发的 reason 透传给 _window.OnDeinitEvent(reason)。reason 是 int 型的去初始化码(比如图表关闭、重编译、手动移除),你可在自己的类里按码做资源释放或日志分级。 OnChartEvent 是鼠标、键盘、自定义事件的总入口。它把 id、lparam、dparam、sparam 四个参数原样喂给 _window.ChartEvent,意味着所有交互逻辑都收敛到窗口对象里处理,EA 主体保持干净。开 MT5 把这段粘进模板,改 _window 类名就能验证事件是否真的被转发。

MQL5 / C++
Print(__FUNCTION__," > Failed to create the graphical interface!");
class="kw">return(INIT_FAILED);
   }
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能系统逆初始化函数                                                  | //+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
  _window.OnDeinitEvent(reason);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                      | //+------------------------------------------------------------------+
class="type">void OnChartEvent(class="kw">const class="type">int    id,
                  class="kw">const class="type">long  &lparam,
                  class="kw">const class="type">class="kw">double &dparam,
                  class="kw">const class="type">class="kw">string &sparam)
  {
  _window.ChartEvent(id,lparam,dparam,sparam);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

用 Sqlite 给优化结果落库

做优化结果后处理,最先被否掉的是写文件:要么拆成多表要么堆成一个行数不齐的大表,维护和分析都别扭。帧(frame)方案本身能跑,但它是 MT 专属,第三方分析工具接不进去;我们要的是任何程序都能读,所以数据库更合适。 筛选条件很硬:快、流行、零依赖、能直接连。Sqlite 全中。DLL 用 C 写,和 MQL5 程序链接成本低,不用为接数据再开一门语言。代价是官方 DLL 没有顺手的 API,得自己包一层最小包装器——这块代码可参考“SQL 和 MQL5:使用 SQLite 数据库”里的导入示例,本项目借了 WinApi 交互和部分导入函数。 包装器落在 MQL5/Include 下的 Sqlite3 和 OptimisationSelector 两个文件夹。WinApi 目录管共享资源(Mutex):优化器和分析器两边同时碰库时,谁先占谁跑,另一方等。Sqlite 允许多线程读,但写必须互斥,否则数据可能残缺。 [CODE] // sqlite_amalgmation.mqh 中导入最小函数集(不足官方 1%) #import "Sqlite3_64.dll" int sqlite3_open(string, int&); int sqlite3_close(int); int sqlite3_exec(int, string, int, int&, string&); int sqlite3_prepare_v2(int, string, int, int&, int&); int sqlite3_step(int); int sqlite3_finalize(int); #import [/CODE] 上面只列了核心导入。DLL 按位数放 Libraries:Sqlite3_32.dll / Sqlite3_64.dll,EA 需勾选允许导入 DLL。CsqliteManager 类管连接与断开(析构里释放),支持文本和参数化两种连接,Connect 重复调用会先断旧库;字符串走 UTF-8 私有转换。Create_sttement 返回语句指针,Exequte 重载跑裸 SQL,事务方法管 begin/commit/rollback。 语句包装器负责填参和销毁:形式如 INSERT INTO table_1 VALUES(@ID,@Param_1),set 方法保存前会对旧 stmt 调 sqlite3_finalize,否则断库时报“busy”。CSqliteReader 在析构调 sqlite3_reset 以便复用,并做字符串到类型的读出转换。 落库结构里 OptimisationParams 是主轴,TradingHistory、ParamsCoefitients 等都用 ID 外键挂它。表列必须带类型(int/double/string),因为后续要按比率排序,得把读出数据还原成原格式。CDataKeeper 类存三种类型+变量名,Compare 只允许同名同类型比,异类返回 Coef_Different。 CDatabaseWriter 给自定义机器人用:OnInitEvent 建库占 Mutex,OnTickEvent 每个 tick 刷分钟烛日期用来判前瞻,OnTesterEvent 在拿 Mutex 后写交易历史与比率。HistoryBorder 字段靠最后日期比对区分历史/前瞻递次——当前日期大于库里值就是前瞻,否则按历史处理或回滚本递次改动。外汇与贵金属品种在此类回测中波动剧烈,实盘前务必用 MT5 多品种多周期复验。

MQL5 / C++
class=class="str">"cmt">// sqlite_amalgmation.mqh 中导入最小函数集(不足官方 class="num">1%)
class="macro">#class="kw">import "Sqlite3_64.dll"
  class="type">int sqlite3_open(class="type">class="kw">string, class="type">int&);
  class="type">int sqlite3_close(class="type">int);
  class="type">int sqlite3_exec(class="type">int, class="type">class="kw">string, class="type">int, class="type">int&, class="type">class="kw">string&);
  class="type">int sqlite3_prepare_v2(class="type">int, class="type">class="kw">string, class="type">int, class="type">int&, class="type">int&);
  class="type">int sqlite3_step(class="type">int);
  class="type">int sqlite3_finalize(class="type">int);
class="macro">#class="kw">import

◍ 差价EA回测数据怎么落库与回读

测试差价交易EA时,程序会先拉取两个标的历史成交,用评测类生成报告再写进SQLite库。写入走事务:先存比率,顺利才存交易历史,最后存买入持有历史——后者只在首次灌数据时写一次,任一步报错就整体回滚,并在Common/Files丢出日志文件。 读库比写库轻。封装类只暴露3个公有方法,覆盖4张表的提取并直接产出结构数组。getBuyAndHold按标志返回前测/历史段买入持有引用,成功返true;getTradingHistory按ID和isForvard取交易历史;getRobotParams则合并ParamsCoefitients与OptimisationParams两张表,顺带带回优化估值比率。 这么封装后,调用方完全不用碰SQL,长期多轮优化累积下来的参数演变形态也更容易追踪。底层走的是开发者提供的Sqlite3_32.dll,库文件本身可随分析程序自由搬运。 下面这段是连接管理类的导入与方法声明,注意所有sqlite3_*均由DLL导出,MT5里直接#import即可调用。

MQL5 / C++
class="macro">#class="kw">import "Sqlite3_32.dll"
class="type">int sqlite3_open(class="kw">const class="type">uchar &filename[],sqlite3_p32 &paDb);class=class="str">"cmt">// 打开数据库
class="type">int sqlite3_close(sqlite3_p32 aDb); class=class="str">"cmt">// 关闭数据库
class="type">int sqlite3_finalize(sqlite3_stmt_p32 pStmt);class=class="str">"cmt">// 完成语句
class="type">int sqlite3_reset(sqlite3_stmt_p32 pStmt); class=class="str">"cmt">// 重置语句
class="type">int sqlite3_step(sqlite3_stmt_p32 pStmt); class=class="str">"cmt">// 读取语句时移至下一行
class="type">int sqlite3_column_count(sqlite3_stmt_p32 pStmt); class=class="str">"cmt">// 计算列数
class="type">int sqlite3_column_type(sqlite3_stmt_p32 pStmt,class="type">int iCol); class=class="str">"cmt">// 获取所选列的类型
class="type">int sqlite3_column_int(sqlite3_stmt_p32 pStmt,class="type">int iCol);class=class="str">"cmt">// 将数值转换为 class="type">int
class="type">long sqlite3_column_int64(sqlite3_stmt_p32 pStmt,class="type">int iCol); class=class="str">"cmt">// 将数值转换为 int64
class="type">class="kw">double sqlite3_column_double(sqlite3_stmt_p32 pStmt,class="type">int iCol); class=class="str">"cmt">// 将数值转换为 class="type">class="kw">double
class="kw">const PTR32 sqlite3_column_text(sqlite3_stmt_p32 pStmt,class="type">int iCol);class=class="str">"cmt">// 获取文本值
class="type">int sqlite3_column_bytes(sqlite3_stmt_p32 apstmt,class="type">int iCol); class=class="str">"cmt">// 从传递的单元格中获取该行占用的字节数
class="type">int sqlite3_bind_int64(sqlite3_stmt_p32 apstmt,class="type">int icol,class="type">long a);class=class="str">"cmt">// 合成请求值(int64 类型)
class="type">int sqlite3_bind_double(sqlite3_stmt_p32 apstmt,class="type">int icol,class="type">class="kw">double a);class=class="str">"cmt">// 合成请求值(class="type">class="kw">double 类型)
class="type">int sqlite3_bind_text(sqlite3_stmt_p32 apstmt,class="type">int icol,class="type">char &a[],class="type">int len,PTRPTR32 destr);class=class="str">"cmt">// 合成请求值 (字符串类型 (C++ 中的 class="type">char*))
class="type">int sqlite3_prepare_v2(sqlite3_p32 db,class="kw">const class="type">uchar &zSql[],class="type">int nByte,PTRPTR32 &ppStmt,PTRPTR32 &pzTail);class=class="str">"cmt">// 准备一个请求
class="type">int sqlite3_exec(sqlite3_p32 aDb,class="kw">const class="type">char &sql[],PTR32 acallback,PTR32 avoid,PTRPTR32 &errmsg);class=class="str">"cmt">// 执行 Sql
class="type">int sqlite3_open_v2(class="kw">const class="type">uchar &filename[],sqlite3_p32 &ppDb,class="type">int flags,class="kw">const class="type">char &zVfs); class=class="str">"cmt">// 依据参数打开数据库
class="macro">#class="kw">import
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 数据库连接和管理类                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSqliteManager
  {
class="kw">public:
                   CSqliteManager(){db=NULL;} class=class="str">"cmt">// 空构造函数
                   CSqliteManager(class="type">class="kw">string dbName); class=class="str">"cmt">// 传递名字
                   CSqliteManager(class="type">class="kw">string dbName,class="type">int flags,class="type">class="kw">string zVfs); class=class="str">"cmt">// 传递名称和连接标志
                   CSqliteManager(CSqliteManager  &other) { db=other.db; } class=class="str">"cmt">// 复制构造函数
                  ~CSqliteManager(){Disconnect();};class=class="str">"cmt">// 析构函数

「SQLite 封装类的连接与语句接口」

在 MT5 里用 MQL5 直接碰 SQLite,核心是一层 C++ 风格的封装类。CSqliteManager 管连接生命周期:Connect 有两种重载,一种按名称直连,一种带 flags 和虚拟文件系统参数,断开统一走 Disconnect。 事务控制给得很完整,BeginTransaction / CommitTransaction / RollbackTransaction 三个方法对应 BEGIN、COMMIT、ROLLBACK。批量写 tick 或订单日志时,用事务包一层,写入耗时通常能压到单条提交的 1/5 以下。 私有方法 stringToUtf8 处理了 MQL5 字符串到 UTF-8 的转码,默认遇到终止符才停(untilTerminator=true),内部调 StringToCharArray 并指定 CP_UTF8。中文表名或备注字段若乱码,先查这里。 CStatement 类负责单条 SQL 语句:构造可传 sqlite3_stmt_p64 指针,析构自动 Sqlite3_finalize,避免句柄泄漏。Execute 支持传回调 statement_callback,查询逐行处理时在回调里返回 true 继续、false 中断。

MQL5 / C++
class="type">void                Disconnect(); class=class="str">"cmt">// 断开与数据库的连接
class="type">bool                Connect(class="type">class="kw">string dbName,class="type">int flags,class="type">class="kw">string zVfs); class=class="str">"cmt">// 与数据库连接的参数
class="type">bool                Connect(class="type">class="kw">string dbName); class=class="str">"cmt">// 按名称连接到数据库
class="type">void class="kw">operator=(CSqliteManager  &other){db=other.db;}class=class="str">"cmt">// 分配操作符
sqlite3_p64 DB() { class="kw">return db; }; class=class="str">"cmt">// 获取指向数据库的指针
sqlite3_stmt_p64  Create_statement(class="kw">const class="type">class="kw">string sql); class=class="str">"cmt">// 创建语句
class="type">bool                Execute(class="type">class="kw">string sql); class=class="str">"cmt">// 执行命令
class="type">void                Execute(class="type">class="kw">string  sql,class="type">int &result_code,class="type">class="kw">string &errMsg); class=class="str">"cmt">// 执行命令,并提供错误代码和消息
class="type">void                BeginTransaction(); class=class="str">"cmt">// 事务开始
class="type">void                RollbackTransaction(); class=class="str">"cmt">// 事务回滚
class="type">void                CommitTransaction(); class=class="str">"cmt">// 确认事务
class="kw">private:
  sqlite3_p64      db; class=class="str">"cmt">// 数据库
  class="type">void stringToUtf8(class="kw">const class="type">class="kw">string strToConvert,class=class="str">"cmt">// 要转换为 utf-class="num">8 编码的字符串数组
                    class="type">uchar &utf8[],class=class="str">"cmt">// 将 utf-class="num">8 编码的数组放入转换后的 strToConvert 字符串中
                    class="kw">const class="type">bool untilTerminator=true)
   {  class=class="str">"cmt">// 转换为 utf-class="num">8 编码的字符数量,并复制到 utf-class="num">8 数组
   class=class="str">"cmt">//---
     class="type">int count=untilTerminator ? -class="num">1 : StringLen(strToConvert);
     StringToCharArray(strToConvert,utf8,class="num">0,count,CP_UTF8);
   }
  };
typedef class="type">bool(*statement_callback)(sqlite3_stmt_p64); class=class="str">"cmt">// 执行查询时要执行的回调。 如果成功,则执行“trueclass CStatement
  {
class="kw">public:
                    CStatement(){stmt=NULL;} class=class="str">"cmt">// 空构造函数
                    CStatement(sqlite3_stmt_p64 _stmt){this.stmt=_stmt;} class=class="str">"cmt">// 含参数的构造函数 - 指向语句的指针
                   ~CStatement(class="type">void){if(stmt!=NULL)Sqlite3_finalize(stmt);} class=class="str">"cmt">// 析构函数
  sqlite3_stmt_p64 get(){class="kw">return stmt;} class=class="str">"cmt">// 获取指向语句的指针
  class="type">void                set(sqlite3_stmt_p64 _stmt); class=class="str">"cmt">// 设置指向语句的指针
  class="type">bool                Execute(statement_callback callback=NULL); class=class="str">"cmt">// 执行语句

读写器的类型映射与析构细节

CSqliteReader 的构造与析构直接绑定 sqlite3_stmt_p64 指针:空构造把 statement 置 NULL,带参构造接收语句指针,析构里调用 Sqlite3_reset(statement) 释放状态。这意味着在 MT5 里如果 reader 对象离开作用域,预编译语句会被自动重置,不会卡住数据库连接。 Parameter() 重载了三套签名,分别接 long、double、string,对应 EA 输入里的整型、浮点、文本三类。底层枚举 DataTypes 把 SQL 的 INTEGER/REAL/Text 映射到 MQL5 的 int/double/string,写库时按这个枚举选列类型,读的时候用 GetInt64、GetDouble、GetText 按列号取,避免类型错配导致静默丢值。 实际验证时可在 EA 里建一个 CSqliteReader 实例,FieldsCount() 返回查询结果的列数,ColumnType(col) 拿到第 col 列的类型代号;若 IsNull(col) 为真说明该格是 SQLITE_NULL,此时调 Get 系列会拿到 0 或空串,最好在取数前先判空。外汇与贵金属行情数据入 SQLite 属高风险操作环境,磁盘写入失败或语句泄漏都可能让回测与实盘信号脱节,需在 OnDeinit 里确认 reader 已全部析构。

MQL5 / C++
class="type">bool Parameter(class="type">int index,class="kw">const class="type">long value); class=class="str">"cmt">// 添加参数
class="type">bool Parameter(class="type">int index,class="kw">const class="type">class="kw">double value); class=class="str">"cmt">// 添加参数
class="type">bool Parameter(class="type">int index,class="kw">const class="type">class="kw">string value); class=class="str">"cmt">// 添加参数
class="kw">private:
  sqlite3_stmt_p64  stmt;
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 从数据库中读取响应的类                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSqliteReader
  {
class="kw">public:
                      CSqliteReader(){statement=NULL;} class=class="str">"cmt">// 空构造函数
                      CSqliteReader(sqlite3_stmt_p64 _statement) { this.statement=_statement; }; class=class="str">"cmt">// 构造函数接收指向语句的指针
                      CSqliteReader(CSqliteReader  &other) : statement(other.statement) {} class=class="str">"cmt">// 复制构造函数
                      ~CSqliteReader() { Sqlite3_reset(statement); } class=class="str">"cmt">// 析构函数
  class="type">void                set(sqlite3_stmt_p64 _statement); class=class="str">"cmt">// 添加语句的引用
  class="type">void class="kw">operator=(CSqliteReader  &other){statement=other.statement;}class=class="str">"cmt">// 读取器分配运算符
  class="type">void class="kw">operator=(sqlite3_stmt_p64 _statement) {set(_statement);}class=class="str">"cmt">// 语句分配运算符
  class="type">bool                Read(); class=class="str">"cmt">// 读取字符串
  class="type">int                 FieldsCount(); class=class="str">"cmt">// 计算列数
  class="type">int                 ColumnType(class="type">int col); class=class="str">"cmt">// 获取列类型
  class="type">bool                IsNull(class="type">int col); class=class="str">"cmt">// 检查数值是否 == SQLITE_NULL
  class="type">long                GetInt64(class="type">int col); class=class="str">"cmt">// 转换为 &class="macro">#x27;class="type">int&class="macro">#x27;
  class="type">class="kw">double              GetDouble(class="type">int col);class=class="str">"cmt">// 转换为 &class="macro">#x27;class="type">class="kw">double&class="macro">#x27;
  class="type">class="kw">string              GetText(class="type">int col);class=class="str">"cmt">// 转换为 &class="macro">#x27;class="type">class="kw">string&class="macro">#x27;
class="kw">private:
  sqlite3_stmt_p64   statement; class=class="str">"cmt">// 指向语句的指针
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA 参数的输入数据类型                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum DataTypes
  {
  Type_INTEGER,class=class="str">"cmt">// class="type">int
  Type_REAL,class=class="str">"cmt">// class="type">class="kw">double, class="type">float
  Type_Text class=class="str">"cmt">// class="type">class="kw">string
  };
把递次比对交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到优化递次与买入持有基准的叠加视图,你只需判断行情类型是否匹配。

常见问题

数据库能在多次优化间做条件过滤和横向比对,文件方式难以快速按滑点、时段筛选百个递次。
按日期区间拆开 PL 图形与比率组,分离不同行情类型的表现,避免被整体曲线掩盖。
程序按优化类型分别审核数据,前瞻段不会接续历史段显示,防止新手误判机器人崩盘。
可以,小布盯盘品种页已内置 AIGC 诊断,支持导入递次统计做横盘/趋势分离视图,省去自己跑脚本。
这类机器人基准对比下常暴露出极端回撤或样本偏差,分析器叠加买入持有曲线就能看清真实位置。