带有图形用户界面的通用震荡指标·进阶篇
(2/3)· 从面向对象基类到表单控件,把 10+ 震荡指标塞进一个带界面的壳里
◍ 给统一震荡指标搭一个基类骨架
在 MT5 的 Include 目录下新建 UniOsc 文件夹,后续所有扩展震荡指标文件都归到这里。先写一份 UniOscDefines.mqh,只放一个枚举 EOscUnyType,把 ATR、BearsPower、BullsPower、CCI、Chaikin、DeMarker、Force、Momentum、MACD、OsMA、RSI、RVI、Stochastic、TriX、WPR 这 15 种震荡类型列清楚,别处也要复用,所以单独成文件最干净。 真正的基类放在 CUniOsc.mqh 里,类名 COscUni。protected 区留作子类可访问的成员,private 不放东西,避免子类拿不到句柄和缓冲区配置。核心方法是 Calculate(),对应指标的 OnCalculate(),只接 rates_total 和 prev_calculated 两个 int,再加 buffer0[]、buffer1[] 两个双精度引用——不传原始价格数组,因为数据走其他指标缓冲区。哪怕某指标只用一条缓冲区,也强制传两条,第二条留作控制。 Calculate() 声明为虚函数,因为不同震荡指标可能用 1 个或 2 个缓冲区,具体逻辑推迟到子类实现。基类先返回 rates_total 占位。 m_handle 放在 protected,记录指标句柄;另有一组变量管显示:m_bufferscnt、m_name、m_label1/2、m_drawtype1/2、m_help、m_digits、m_levels_total 和 m_level_value[]。这些在子类加载具体指标时赋值。CheckHandle() 用 m_handle!=INVALID_HANDLE 判断加载是否成功;Handle() 直接返回句柄,供 BarsCalculated() 查计算进度,图形界面切指标时能知道是不是算完了。 构造里把 m_handle 置为 INVALID_HANDLE,析构里若句柄有效就调 IndicatorRelease() 释放,防止内存泄漏。剩下的 Name()、BuffersCount()、Label1()、Label2()、DrawType1/2()、Help()、Digits()、LevelsTotal()、LevelValue() 全是只读 getter,返回对应变量,值由子类填。 开 MT5 照这个结构建好两个文件,编译能过就说明基类骨架立住了,接下来往里塞具体指标的子类即可。外汇与贵金属指标开发涉及实盘逻辑,回测与加载失败均属高风险操作,需自行验证。
enum EOscUnyType{ OscUni_ATR, OscUni_BearsPower, OscUni_BullsPower, OscUni_CCI, OscUni_Chaikin, OscUni_DeMarker, OscUni_Force, OscUni_Momentum, OscUni_MACD, OscUni_OsMA, OscUni_RSI, OscUni_RVI, OscUni_Stochastic, OscUni_TriX, OscUni_WPR }; class COscUni{ class="kw">protected: class="kw">public: }; class="kw">virtual class="type">int Calculate( class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="type">class="kw">double & buffer0[], class="type">class="kw">double & buffer1[] ){ class="kw">return (rates_total); } class="type">int m_handle; class=class="str">"cmt">// 指标句柄 class="type">int m_bufferscnt; class=class="str">"cmt">// 使用的缓冲区数量 class="type">class="kw">string m_name; class=class="str">"cmt">// 指标名称 class="type">class="kw">string m_label1; class=class="str">"cmt">// 缓冲区1的名称 class="type">class="kw">string m_label2; class=class="str">"cmt">// 缓冲区2的名称 class="type">int m_drawtype1; class=class="str">"cmt">// 缓冲区1的绘图类型 class="type">int m_drawtype2; class=class="str">"cmt">// 缓冲区2的绘图类型 class="type">class="kw">string m_help; class=class="str">"cmt">// 指标参数的提示 class="type">int m_digits; class=class="str">"cmt">// 指标值中小数点位数 class="type">int m_levels_total; class=class="str">"cmt">// 水平总数 class="type">class="kw">double m_level_value[]; class=class="str">"cmt">// 水平数值的数组 class="type">bool CheckHandle(){ class="kw">return (m_handle!= INVALID_HANDLE ); } class="type">int Handle(){ class="kw">return (m_handle); } class="type">void COscUni(){ m_handle= INVALID_HANDLE ; } class="type">void ~COscUni(){ if (m_handle!= INVALID_HANDLE ){ IndicatorRelease(m_handle); } } class="type">class="kw">string Name(){ class=class="str">"cmt">// 震荡指标的名称 class="kw">return (m_name); } class="type">int BuffersCount(){ class=class="str">"cmt">// 震荡指标缓冲区的数量 class="kw">return (m_bufferscnt); } class="type">class="kw">string Label1(){ class=class="str">"cmt">// 第一个缓冲区的名称 class="kw">return (m_label1); } class="type">class="kw">string Label2(){ class=class="str">"cmt">// 第二个缓冲区的名称 class="kw">return (m_label2); } class="type">int DrawType1(){ class=class="str">"cmt">// 第一个缓冲区的绘图类型 class="kw">return (m_drawtype1); } class="type">int DrawType2(){ class=class="str">"cmt">// 第二个缓冲区的绘图类型 class="kw">return (m_drawtype2); } class="type">class="kw">string Help(){ class=class="str">"cmt">// 使用参数的提示 class="kw">return (m_help); } class="type">int Digits(){ class=class="str">"cmt">// 指标值的小数点位数 class="kw">return (m_digits); } class="type">int LevelsTotal(){ class=class="str">"cmt">// 指标水平的数量 class="kw">return (m_levels_total); } class="type">class="kw">double LevelValue( class="type">int index){ class=class="str">"cmt">// 根据指定的索引取得水平的数值 class="kw">return (m_level_value[index]); }
单缓冲与双缓冲指标的计算子类
把通用振荡器再往下拆一层,会落到两个具体子类:只吃一个缓冲区的 COscUni_Calculate1,以及吃两个缓冲区的 COscUni_Calculate2。前者在构造函数里把 m_bufferscnt 写死为 1,后者写死为 2,缓冲区个数在对象生成时就定调,后面 Calculate 不再改。 COscUni_Calculate1 的 Calculate 方法先根据 prev_calculated 算 cnt 和 start:首次跑(prev_calculated==0)时 cnt=rates_total、start=0;后续刷新时 cnt=rates_total-prev_calculated+1、start=prev_calculated-1。随后用 CopyBuffer 把指标句柄的第 0 号缓冲拉进 buffer0,失败直接 return(0),迫使下一 tick 重算。空出来的 buffer1 从 start 到 rates_total 全填 EMPTY_VALUE,最后回 rates_total。 双缓冲版本更直白:cnt 算法完全一致,但连续两次 CopyBuffer 分别把 0 号和 1 号缓冲搬进 buffer0、buffer1,任一次失败就 return(0)。没有清空辅助缓冲的动作,因为两个缓冲都有真实数据落地。 在 MT5 里把这段抄进 EA 或指标工程,改 m_handle 指向你用的振荡器,就能实测首次加载与后续刷新的拷贝行数差异——外汇与贵金属波动大,这类拷贝边界处理不当可能在高波动时漏柱,属高风险验证项。
class COscUni_Calculate1: class="kw">public COscUni{ class="kw">public: class="type">void COscUni_Calculate1(){ m_bufferscnt=class="num">1; } class="kw">virtual class="type">int Calculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="type">class="kw">double &buffer0[], class="type">class="kw">double &buffer1[] ){ class="type">int cnt,start; if(prev_calculated==class="num">0){ cnt=rates_total; start=class="num">0; } else{ cnt=rates_total-prev_calculated+class="num">1; start=prev_calculated-class="num">1; } if(CopyBuffer(m_handle,class="num">0,class="num">0,cnt,buffer0)<=class="num">0){ class="kw">return(class="num">0); } for(class="type">int i=start;i<rates_total;i++){ buffer1[i]=EMPTY_VALUE; } class="kw">return(rates_total); } }; class COscUni_Calculate2: class="kw">public COscUni{ class="kw">public: class="type">void COscUni_Calculate2(){ m_bufferscnt=class="num">2; } class="kw">virtual class="type">int Calculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="type">class="kw">double &buffer0[], class="type">class="kw">double &buffer1[] ){ class="type">int cnt; if(prev_calculated==class="num">0){ cnt=rates_total; } else{ cnt=rates_total-prev_calculated+class="num">1; } if(CopyBuffer(m_handle,class="num">0,class="num">0,cnt,buffer0)<=class="num">0){ class="kw">return(class="num">0); } if(CopyBuffer(m_handle,class="num">1,class="num">0,cnt,buffer1)<=class="num">0){ class="kw">return(class="num">0); } class="kw">return(rates_total); } };
「给震荡指标写子类时的参数接管逻辑」
通用震荡框架里,每个具体指标都该是一个独立子类,统一继承自 COscUni_Calculate1 或 COscUni_Calculate2。子类只留一个构造函数,把指标自身参数外加两个控制开关 use_default、keep_previous 一并收进来。use_default 决定要不要填默认值,keep_previous 则控制是全覆盖还是只补那些还没被碰过的参数。 ATR 子类是最省事的例子。ma_period 用引用传参,是为了在通用层设默认值时能反向写回。构造函数里这段判断是核心:当 use_default 且 keep_previous 为真,仅当 ma_period 还等于 -1 才赋 14;否则直接拍成 14。也就是说,通用层初始化前必须把所有参数先置 -1,才能靠这个机制识别“没用过”。 m_handle = iATR(Symbol(), Period(), ma_period); 这一行才真正把指标句柄建出来。后面几行纯属显示配置:m_name 拼成 "ATR(14)" 这类标签,m_drawtype1 设 DRAW_LINE,m_digits 取 _Digits+1 让小数位多一位,m_levels_total 给 0 表示不带水平线。 MACD 套路一样但字段更密。fast_ema_period 默认 12、slow_ema_period 默认 26、signal_period 默认 9、applied_price 默认 PRICE_CLOSE,keep_previous 下同样靠 -1 判断补缺。注意 applied_price 在子类原型里是 long&,标准枚举 ENUM_APPLIED_PRICE 本就是长整,用 -1 当“未设”哨兵不会类型冲突。iMACD 句柄建好后,m_drawtype1 配 DRAW_HISTOGRAM 画柱、m_drawtype2 配 DRAW_LINE 画信号线。 RSI 子类多一小段水平线配置:m_levels_total=3,数组扩到 3 位,分别填 30、50、70。这三档是裸眼读超买超卖的常用参照,外汇与贵金属波动剧烈,RSI 触 70/30 只代表概率倾斜,不代表反转必至,实操仍属高风险。其余震荡指标子类写法同构,完整集已在配套 CUniOsc.mqh 里,开 MT5 拖进去就能编译验证。
class COscUni_ATR: class="kw">public COscUni_Calculate1{ class="kw">public: class="type">void COscUni_ATR(class="type">bool use_default, class="type">bool keep_previous, class="type">int &ma_period){ } }; if(use_default){ if(keep_previous){ if(ma_period==-class="num">1)ma_period=class="num">14; } else{ ma_period=class="num">14; } } m_handle=iATR(Symbol(),Period(),ma_period); m_name=StringFormat("ATR(%i)",ma_period); m_label1="ATR"; m_drawtype1=DRAW_LINE; m_help=StringFormat("ma_period - Period1(%i)",ma_period); m_digits=_Digits+class="num">1; m_levels_total=class="num">0; if(use_default){ if(keep_previous){ if(fast_ema_period==-class="num">1)fast_ema_period=class="num">12; if(slow_ema_period==-class="num">1)slow_ema_period=class="num">26; if(signal_period==-class="num">1)signal_period=class="num">9; if(applied_price==-class="num">1)applied_price=PRICE_CLOSE; } else{ fast_ema_period=class="num">12; slow_ema_period=class="num">26; signal_period=class="num">9; applied_price=PRICE_CLOSE; } } m_handle=iMACD(Symbol(), Period(), fast_ema_period, slow_ema_period, signal_period, (ENUM_APPLIED_PRICE)applied_price); m_name=StringFormat("iMACD(%i,%i,%i,%s)", fast_ema_period, slow_ema_period, signal_period, EnumToString((ENUM_APPLIED_PRICE)applied_price)); m_label1="Main"; m_label2="Signal"; m_drawtype1=DRAW_HISTOGRAM; m_drawtype2=DRAW_LINE; m_help=StringFormat("fast_ema_period - Period1(%i), "+ "slow_ema_period - Period2(%i), "+ "signal_period - Period3(%i), "+ "applied_price - Price(%s)", fast_ema_period, slow_ema_period, signal_period, EnumToString((ENUM_APPLIED_PRICE)applied_price)); m_digits=_Digits+class="num">1; class="type">void COscUni_MACD(class="type">bool use_default, class="type">bool keep_previous, class="type">int &fast_ema_period, class="type">int &slow_ema_period, class="type">int &signal_period, class="type">long &applied_price){ m_levels_total=class="num">3; ArrayResize(m_level_value,class="num">3); m_level_value[class="num">0]=class="num">30; m_level_value[class="num">1]=class="num">50; m_level_value[class="num">2]=class="num">70;
◍ 搭一个可换类型的通用震荡指标骨架
震荡指标的类已经就绪,下一步是在 MT5 里新建一个名为 iUniOsc 的指标,向导里选 OnCalculate(...open,high,low,close) 这种带 OHLC 的重载,再开一个外部变量和两个线缓冲区。先把两个头文件塞到外部变量前:UniOscDefines.mqh 管枚举,CUniOsc.mqh 管通用类,不然后面编译器不认类型。 外部参数里先放一个 EOscUnyType 型变量 Type,默认 OscUni_ATR,再用 UseDefault 和 KeepPrev 两个 bool 控制是否走类里的默认值与保留前值逻辑。周期类参数统一给 Period1/2/3 = 14,MaMethod 默认 MODE_EMA,Price 默认 PRICE_CLOSE,Volume 默认 VOLUME_TICK,StPrice 默认 STO_LOWHIGH——这些就是后面 GUI 没接管前你能直接调的旋钮。 线颜色分三套:ColorLine1 亮绿画主线、ColorLine2 红画副线、ColorHisto 灰画柱。因为后面要做不重启就能换类型的 GUI,所以得给 Type 和那堆参数各留一份带下划线的副本变量(_Period1 等),以及 COscUni *osc 这个对象指针和 ProgName / ShortName 两个名字串。 PrepareParameters() 里有个细节:UseDefault 且 KeepPrev 同时为真时,所有 _Period 和 _MaMethod 等全赋 -1,类的构造函数看到 -1 就知道跳过去用内置默认,否则才用属性窗口的值。LoadOscillator() 按 _Type 走 switch 去 new 对应的子类(ATR、BearsPower、BullsPower…),new 完必须 osc.CheckHandle() 判错,失败就 Alert 并返回 INIT_FAILED。 SetStyles() 根据 BuffersCount() 是 1 还是 2 决定画一条还是两条:缓冲区 0 若是 DRAW_HISTOGRAM 就染成灰色,否则亮绿;缓冲区 1 存在才显示并染红。末尾用 IndicatorSetInteger(INDICATOR_DIGITS, osc.Digits()) 对齐小数位,再用 osc.LevelsTotal() 把水平线一次性灌进去。 OnInit() 里依次调 PrepareParameters → LoadOscillator → CheckHandle → SetStyles,最后 Print(osc.Help()) 把当前参数映射打到日志,并拼好 ShortName 设给 INDICATOR_SHORTNAME。到这步骨架就能跑,附件里的 iUniOsc 就是这版,后面改 GUI 时它会和现在略有出入。
class="macro">#include <UniOsc/UniOscDefines.mqh> class="macro">#include <UniOsc/CUniOsc.mqh> input EOscUnyType Type = OscUni_ATR; input class="type">bool UseDefault = true; input class="type">bool KeepPrev = true; input class="type">int Period1 = class="num">14; input class="type">int Period2 = class="num">14; input class="type">int Period3 = class="num">14; input ENUM_MA_METHOD MaMethod = MODE_EMA; input ENUM_APPLIED_PRICE Price = PRICE_CLOSE; input ENUM_APPLIED_VOLUME Volume = VOLUME_TICK; input ENUM_STO_PRICE StPrice = STO_LOWHIGH; input class="type">class="kw">color ColorLine1 = clrLightSeaGreen; input class="type">class="kw">color ColorLine2 = clrRed; input class="type">class="kw">color ColorHisto = clrGray; class="type">int _Period1; class="type">int _Period2; class="type">int _Period3; class="type">long _MaMethod; class="type">long _Price; class="type">long _Volume; class="type">long _StPrice; EOscUnyType _Type; COscUni * osc; class="type">class="kw">string ProgName; class="type">class="kw">string ShortName; class="type">void PrepareParameters(){ _Type=Type; if (UseDefault && KeepPrev){ _Period1=-class="num">1; _Period2=-class="num">1; _Period3=-class="num">1; _MaMethod=-class="num">1; _Volume=-class="num">1; _Price=-class="num">1; _StPrice=-class="num">1; } else { _Period1=Period1; _Period2=Period2; _Period3=Period3; _MaMethod=MaMethod; _Volume= Volume; _Price=Price; _StPrice=StPrice; } } class="type">void LoadOscillator(){ class="kw">switch (_Type){ case OscUni_ATR: osc= new COscUni_ATR(UseDefault,KeepPrev,_Period1); class="kw">break; case OscUni_BearsPower: osc= new COscUni_BearsPower(UseDefault,KeepPrev,_Period1); class="kw">break; case OscUni_BullsPower: osc= new COscUni_BullsPower(UseDefault,KeepPrev,_Period1); class="kw">break; } } class="type">void SetStyles(){ if (osc.BuffersCount()==class="num">2){ PlotIndexSetInteger(class="num">0,PLOT_DRAW_TYPE,osc.DrawType1()); PlotIndexSetInteger(class="num">1,PLOT_DRAW_TYPE,osc.DrawType2()); PlotIndexSetInteger(class="num">0,PLOT_SHOW_DATA,true); PlotIndexSetInteger(class="num">1,PLOT_SHOW_DATA,true); PlotIndexSetString(class="num">0,PLOT_LABEL,osc.Label1()); PlotIndexSetString(class="num">1,PLOT_LABEL,osc.Label2()); if (osc.DrawType1()==DRAW_HISTOGRAM){ PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,ColorHisto); } else { PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,ColorLine1); } PlotIndexSetInteger(class="num">1,PLOT_LINE_COLOR,ColorLine2); } else { PlotIndexSetInteger(class="num">0,PLOT_DRAW_TYPE,osc.DrawType1()); PlotIndexSetInteger(class="num">1,PLOT_DRAW_TYPE,DRAW_NONE); PlotIndexSetInteger(class="num">0,PLOT_SHOW_DATA,true); PlotIndexSetInteger(class="num">1,PLOT_SHOW_DATA,class="kw">false); PlotIndexSetString(class="num">0,PLOT_LABEL,osc.Label1()); PlotIndexSetString(class="num">1,PLOT_LABEL,""); if (osc.DrawType1()==DRAW_HISTOGRAM){ PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,ColorHisto); } else { PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,ColorLine1); } } IndicatorSetInteger(INDICATOR_DIGITS,osc.Digits()); class="type">int levels=osc.LevelsTotal(); IndicatorSetInteger(INDICATOR_LEVELS,levels); for (class="type">int i=class="num">0;i<levels;i++){ IndicatorSetDouble(INDICATOR_LEVELVALUE,i,osc.LevelValue(i)); } } class="type">int OnInit(){ SetIndexBuffer(class="num">0,Label1Buffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,Label2Buffer,INDICATOR_DATA); PrepareParameters(); LoadOscillator(); if (!osc.CheckHandle()){ Alert("载入指标时出错 "+osc.Name()); class="kw">return(INIT_FAILED); } SetStyles(); Print("参数对应: "+osc.Help()); ShortName=ProgName+": "+osc.Name(); IndicatorSetString(INDICATOR_SHORTNAME,ShortName); class="kw">return(INIT_SUCCEEDED); }
选 incGUI 搭面板前的落地动作
在 MT5 里做图形界面,路子不止一条:用原生图形对象手搓输入框和按钮是一种,也能直接搬社区里各种 MQL5 界面库,或者用终端自带的标准面板类。写这套系统时我把几类方案都过了一遍,最终落到 incGUI 开发库上——它用图形对象封装出表单、带调节钮的输入框和下拉列表,省掉大量底层绘制代码。 这个库出自「自定义图形化控件」系列第三篇的附件,核心是 incGUI_v3.mqh。MetaTrader 5 仍在频繁迭代,库里个别控件(比如滚动条)以后可能和终端新机制脱节,但当前版本照用不误。 落地只需一步:下载那篇第三部分的附件,解压后把 incGUI_v3.mqh 丢进终端数据目录的 MQL5/Include 下。重开 MT5 编辑器就能 #include <incGUI_v3.mqh> 起手,外汇与贵金属品种波动剧烈、杠杆风险高,界面只是辅助,信号逻辑仍须自行回测验证。
「把振荡器面板做成可交互表单」
图形界面单独放在 UniOscGUI.mqh 里写,先引 IncGUI_v4.mqh 和 UniOscDefines.mqh,再把 iUniOsc 复制成 iUniOscGUI,隐藏掉无意义的 UseDefault 和 KeepPrev 并强制置 false。iUniOscGUI 共包含三个头文件:UniOscDefines、CUniOsc、UniOscGUI,编译后图表上直接出 GUI。 表单类从 CFormTemplate 抄进 UniOscGUI.mqh 并重命名为 CUniOscForm,MainProperties() 里把宽度锁死为 FORM_WIDTH(210)、高度 150、可移动可缩放带关闭键。文件开头要定义三个常数:FORM_WIDTH=210、SPIN_BOX_WIDTH=110、COMBO_BOX_WIDTH=110,后期调控件尺寸就改这几个值。 指标里声明 input bool UseGUI=true,OnInit() 中 new 出表单并 Init、设子窗口、摆到 (10,30) 再 Show;OnDeinit() 里 Hide 加 delete。OnChartEvent() 把事件透传给 frm.Event()。点关闭按钮时 Event() 返回 1,用 ChartWindowFind(0,ShortName) 拿子窗口索引,再 ChartIndicatorDelete 把指标从图表删掉,外汇/贵金属图表加载自定义指标本就高风险,误删重加成本不高但打断盯盘节奏。 下拉列表用 CComBox 做,m_cmb_main.Init 传前缀名、宽度 100、标签“选择震荡指标”,OnShowEvent 里偏移 (10,10) 显示。选择变化事件不从表单内部处理,而在指标 OnChartEvent 调 m_cmb_main.Event,返回 1 就 Alert 当前 SelectedText。 填充列表走最省事的第 3 种:指标里建 EOscUniType 数组,涵盖 ATR 到 WPR 共 15 个振荡器,frm.Init 之后循环 AddItem(EnumToString) 并 SetSelectedIndex(0)。此时图表表单已能下拉且切换弹框,MT5 里加载验证一遍即可确认。
class="macro">#include <IncGUI_v4.mqh> class="macro">#include <UniOsc/UniOscDefines.mqh> class="macro">#include <UniOsc/CUniOsc.mqh> class="macro">#include <UniOsc/UniOscGUI.mqh> class="type">bool UseDefault = class="kw">false; class="type">bool KeepPrev = class="kw">false; input class="type">bool UseGUI = true; CUniOscForm * frm; class="type">void MainProperties(){ m_Name = "UniOscForm"; m_Width = FORM_WIDTH; m_Height = class="num">150; m_Type = class="num">0; m_Caption = "UniOsc"; m_Movable = true; m_Resizable = true; m_CloseButton = true; } class="macro">#define FORM_WIDTH class="num">210 class="macro">#define SPIN_BOX_WIDTH class="num">110 class="macro">#define COMBO_BOX_WIDTH class="num">110 class="type">int OnInit(){ if(UseGUI){ frm = new CUniOscForm(); frm.Init(); frm.SetSubWindow(class="num">0); frm.SetPos(class="num">10,class="num">30); frm.Show(); } class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(class="kw">const class="type">int reason){ if(CheckPointer(frm)==POINTER_DYNAMIC){ frm.Hide(); class="kw">delete(frm); } } 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){ frm.Event(id,lparam,dparam,sparam); class="type">int me=frm.m_cmb_main.Event(id,lparam,dparam,sparam); if(me==class="num">1){ Alert(frm.m_cmb_main.SelectedText()); } } EOscUniType osctype[]={ OscUni_ATR, OscUni_BearsPower, OscUni_BullsPower, OscUni_CCI, OscUni_Chaikin, OscUni_DeMarker, OscUni_Force, OscUni_Momentum, OscUni_MACD, OscUni_OsMA, OscUni_RSI, OscUni_RVI, OscUni_Stochastic, OscUni_TriX, OscUni_WPR }; for(class="type">int i=class="num">0;i<ArraySize(osctype);i++){ frm.m_cmb_main.AddItem(EnumToString(osctype[i])); } frm.m_cmb_main.SetSelectedIndex(class="num">0); class="type">int win=ChartWindowFind(class="num">0,ShortName); ChartIndicatorDelete(class="num">0,win,ShortName); ChartRedraw();