海龟汤和海龟汤升级版的改进·进阶篇
(2/3)· 带着基础篇的通道定义,我们用结构和信号函数把假突破反弹策略写成可跑的 EA
不少交易者把海龟汤当成简单反转信号直接用,忽略了 20 天通道参数和假突破判定在 MQL5 里需要明确的结构化实现。照搬书本年份的历史表现,不等于近年的 MetaQuotes 模拟数据还能复现同样的概率分布。
用签名比对判断通道是否重算
通道类在每次刷新时,会先取出时间区间的起止点:t_From 取最近一根 K 线时间,t_To 取第 i_Price_Bars-1 根的时间,也就是回看窗口的最远端。这套取值只在 else 分支生效,意味着前置条件不满足时才走默认窗口。 随后用 StringFormat 拼出一个 5 位小数的低点、5 位高点、起止时间的字符串签名。MQL5 里 DoubleToString 配合 _Digits 能保证输出和当前品种报价精度一致,TimeToString 带 TIME_DATE|TIME_MINUTES 则把秒抹掉,避免同分钟内的无意义刷新。 签名一变就置 b_Updated=true,并打印日志(仅当 Log_Level>LOG_LEVEL_ERR)。这个设计让你在 MT5 Experts 面板里能直接看到“通道已更新”及具体边界,而不必每次重画对象。最后 b_Ready=true 标记本轮数据提交成功,后续指标绘制才敢读 go_Channel。 外汇与贵金属波动剧烈,这种基于签名跳过的机制可能降低 CPU 占用,但也可能漏掉同分钟内的快速破位,实盘前建议在策略测试器里用不同 i_Price_Bars 跑一遍确认。
else { t_From = ta_Time_Array[class="num">0]; t_To = ta_Time_Array[i_Price_Bars - class="num">1]; } class="type">class="kw">string s_New_Signature = StringFormat("%.5f%.5f%u%u", d_Low, d_High, t_From, t_To); if(s_Signature != s_New_Signature) { class=class="str">"cmt">// 通道数据已经改变 b_Updated = true; if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 通道已更新: %s .. %s / %s .. %s, min: %u max: %u ", __FUNCTION__, DoubleToString(d_Low, _Digits), DoubleToString(d_High, _Digits), TimeToString(t_From, TIME_DATE|TIME_MINUTES), TimeToString(t_To, TIME_DATE|TIME_MINUTES), i_Lowest_Offset, i_Highest_Offset); s_Signature = s_New_Signature; } b_Ready = true; class=class="str">"cmt">// 数据更新成功完成 } }; CHANNEL go_Channel;
◍ 把海龟汤信号写进一个可切换的函数
买入触发需要两层过滤:距离前一个 20 天低点至少过去 3 个交易日,且价格低于 20 天低点(基础版)或前一日收盘不高于 20 天低点(升级版)。卖出逻辑镜像处理,看的是前期高点与通道上边界的关系。订单参数和仓位管理不进信号模块,只负责返回「买 / 卖 / 无」三态。 为了把两种版本塞进同一个 EA,用枚举 ENUM_STRATEGY 做开关比 bool 更利于后面扩策略;实例里只有 TS_TURTLE_SOUP 和 TS_TURTLE_SOUP_PLUS_1 两项。主程序把 b_Wait_For_Bar_Close 和 i_Extremum_Bars=3 传进 fe_Get_Entry_Signal,函数靠全局 MqlTick 结构 go_Tick 拿最新报价。 基础版用 go_Tick.bid 作当前价;升级版等柱收完,用 CopyClose 取前一根收盘。函数每 tick 都可能被调,但第一个条件(极值点偏移 > 3 根)不满足时直接睡到下一根,避免无谓的通道读取——这是实盘里省资源的关键。 通道对象可能还没就绪(b_Ready=false),加上标志检查与 CopyClose 的错误分支,否则回测时容易静默漏信号。最终文件存为 Signal_Turtle_Soup.mqh,丢进 MQL5\Include\Expert\Signal 目录即可被主 EA 包含。外汇与贵金属波动剧烈,信号仅表示条件满足概率,不代表后市必然反转。
enum ENUM_STRATEGY { class=class="str">"cmt">// 策略列表 TS_TURTLE_SOUP, class=class="str">"cmt">// 海龟汤 TS_TURTLE_SOUP_PLUS_1 class=class="str">"cmt">// 海龟汤升级版 }; class="kw">input ENUM_STRATEGY Strategy_Type = TS_TURTLE_SOUP; class=class="str">"cmt">// 交易策略: enum ENUM_ENTRY_SIGNAL { class=class="str">"cmt">// 进场信号的列表 ENTRY_BUY, class=class="str">"cmt">// 买入信号 ENTRY_SELL, class=class="str">"cmt">// 卖出信号 ENTRY_NONE, class=class="str">"cmt">// 没有信号 ENTRY_UNKNOWN class=class="str">"cmt">// 未定义状态 }; class="type">MqlTick go_Tick; class=class="str">"cmt">// 最后所知订单时刻的信息 ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal( class="type">bool b_Wait_For_Bar_Close = class="kw">false, class="type">int i_Extremum_Bars = class="num">3 ) {} if(go_Channel.i_Highest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_High < d_Actual_Price) class=class="str">"cmt">// 第二个条件 class="kw">return(ENTRY_SELL); class=class="str">"cmt">// 两个卖出条件都已达到 if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_Low > d_Actual_Price) { class=class="str">"cmt">// 第二个条件 class="kw">return(ENTRY_BUY); class=class="str">"cmt">// 两个买入条件都已满足 class="type">class="kw">double d_Actual_Price = go_Tick.bid; class=class="str">"cmt">// 默认价格 - 用于海龟汤版本 if(b_Wait_For_Bar_Close) { class=class="str">"cmt">// 用于海龟汤升级版版本 class="type">class="kw">double da_Price_Array[class="num">1]; class=class="str">"cmt">// 辅助数组 CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">1, da_Price_Array)); d_Actual_Price = da_Price_Array[class="num">0]; } ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(class="type">bool b_Wait_For_Bar_Close = class="kw">false, class="type">int i_Extremum_Bars = class="num">3) { class="type">class="kw">double d_Actual_Price = go_Tick.bid; class=class="str">"cmt">// 默认价格 - 用于海龟汤版本 if(b_Wait_For_Bar_Close) { class=class="str">"cmt">// 用于海龟汤升级版版本 class="type">class="kw">double da_Price_Array[class="num">1]; CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">1, da_Price_Array)); d_Actual_Price = da_Price_Array[class="num">0]; } class=class="str">"cmt">// 上方边界: if(go_Channel.i_Highest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_High < d_Actual_Price) { class=class="str">"cmt">// 2nd condition class=class="str">"cmt">// 价格突破了上方边界 class="kw">return(ENTRY_SELL); } class=class="str">"cmt">// 下方边界: if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_Low > d_Actual_Price) { class=class="str">"cmt">// 第二个条件 class=class="str">"cmt">// 价格突破了下方边界 class="kw">return(ENTRY_BUY); } class="kw">return(ENTRY_NONE); }
「通道突破信号的两种取价逻辑」
海龟汤类策略里,突破信号的核心在于「取哪一刻的价」。下面这个函数给了两条路:实时买价(bid)用于原始版,收盘确认价用于升级版,由 b_Wait_For_Bar_Close 开关切换。 函数开头先拦掉通道未就绪的情况:go_Channel.b_Ready 为 false 时直接回 ENTRY_UNKNOWN,避免拿脏数据出信号。若开了等收盘,则用 CopyClose(_Symbol, PERIOD_CURRENT, 1, 1, da_Price_Array) 取前一根收盘价;返回值等于 WRONG_VALUE 就报 _LastError 并回 ENTRY_NONE。 突破判定分上下边界。上边界要求 i_Highest_Offset > i_Extremum_Bars(默认 3 根)且 d_High < d_Actual_Price,满足回 ENTRY_SELL;下边界对称,回 ENTRY_BUY。两者都不触发就回 ENTRY_NONE,说明价还在通道内。 第二个重载版本多了一个静态暂停钟 st_Pause_End:若当前 tick 时间小于它,直接回 ENTRY_NONE 跳过,等于给通道重算留冷却。若上下偏移都不足 i_Extremum_Bars,也判第一个条件不满足并进入暂停,等通道刷新再评。外汇与贵金属杠杆高,通道假突破概率不低,参数 i_Extremum_Bars 建议先在 MT5 历史数据上跑一遍再上实盘。
ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(class="type">bool b_Wait_For_Bar_Close = class="kw">false, class="type">int i_Extremum_Bars = class="num">3) { if(!go_Channel.b_Ready) { class=class="str">"cmt">// 通道数据还没有准备好 if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 通道参数没有准备好", __FUNCTION__); class="kw">return(ENTRY_UNKNOWN); } class="type">class="kw">double d_Actual_Price = go_Tick.bid; class=class="str">"cmt">// 默认价格 - 用于海龟汤版本 if(b_Wait_For_Bar_Close) { class=class="str">"cmt">// 用于海龟汤升级版版本 class="type">class="kw">double da_Price_Array[class="num">1]; if(WRONG_VALUE == CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">1, da_Price_Array)) { class=class="str">"cmt">// 处理 CopyClose 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyClose: 错误 #%u", __FUNCSIG__, _LastError); class="kw">return(ENTRY_NONE); } d_Actual_Price = da_Price_Array[class="num">0]; } class=class="str">"cmt">// 上方边界: if(go_Channel.i_Highest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_High < d_Actual_Price) { class=class="str">"cmt">// 2nd condition class=class="str">"cmt">// 价格突破了上方边界 if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 价格 (%s) 已经突破了上方边界 (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_High, _Digits)); class="kw">return(ENTRY_SELL); } class=class="str">"cmt">// 下方边界: if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_Low > d_Actual_Price) { class=class="str">"cmt">// 第二个条件 class=class="str">"cmt">// 价格突破了下方边界 if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 价格 (%s) 已经突破了下方边界 (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_Low, _Digits)); class="kw">return(ENTRY_BUY); } class=class="str">"cmt">// 如果程序达到了这一行,价格就在范围之内,也就是第二个条件不满足 class="kw">return(ENTRY_NONE); } ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(class="type">bool b_Wait_For_Bar_Close = class="kw">false, class="type">int i_Extremum_Bars = class="num">3) { class="kw">static class="type">class="kw">datetime st_Pause_End = class="num">0; class=class="str">"cmt">// 下一次检查的时间 if(st_Pause_End > go_Tick.time) class="kw">return(ENTRY_NONE); st_Pause_End = class="num">0; if(go_Channel.b_In_Process) { class=class="str">"cmt">// 通道数据还没有准备好 if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 通道参数没有准备好", __FUNCTION__); class="kw">return(ENTRY_UNKNOWN); } if(go_Channel.i_Lowest_Offset < i_Extremum_Bars && go_Channel.i_Highest_Offset < i_Extremum_Bars) { class=class="str">"cmt">// 第一个条件不满足 if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 第一个条件不满足", __FUNCTION__); class=class="str">"cmt">// 暂停,直到通道更新
海龟汤两种版本的边界突破判定
这段代码给出的是通道边界被触碰时的返回逻辑,核心区分在于「海龟汤」与「海龟汤升级版」两种模式对价格取样的差异。升级版通过 CopyClose 取前一根已收市 K 线的收盘价作为 d_Actual_Price,而基础版直接用当前 bid。 上方边界判断要求最高价偏移柱数大于设定的 i_Extremum_Bars,且实际价格高于通道高点 go_Channel.d_High,此时返回 ENTRY_SELL;下方边界对称,实际价格低于通道低点则返回 ENTRY_BUY。若两个条件都不满足,价格仍在通道内,函数落到末尾返回 ENTRY_NONE。 输入参数里 Turtle_Soup_Period_Length 默认 20 根柱搜索极值,Turtle_Soup_Extremum_Offset 默认 3 根柱作为暂停距离,Entry_Offset 为 10 点偏移、Exit_Offset 为 1 点偏移。外汇与贵金属杠杆高,通道假突破概率不低,参数直接照搬可能频繁止损。 升级版在收线后才比对边界,因此末尾会把 st_Pause_End 推到当前柱结束时间,避免同一根 K 线内重复触发。想验证的话,把 Turtle_Soup_Type 切到 TS_TURTLE_SOUP_PLIS_1,在 MT5 策略测试器用 20 根极值深度跑 EURUSD 小时图,观察回测里 ENTRY_SELL/BUY 的触发时刻是否总落在前一根收盘之后。
st_Pause_End = go_Tick.time + PeriodSeconds() - go_Tick.time % PeriodSeconds(); class="kw">return(ENTRY_NONE); } class="type">class="kw">double d_Actual_Price = go_Tick.bid; class=class="str">"cmt">// 默认价格 - 用于海龟汤版本 if(b_Wait_For_Bar_Close) { class=class="str">"cmt">// 用于海龟汤升级版版本 class="type">class="kw">double da_Price_Array[class="num">1]; if(WRONG_VALUE == CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">1, da_Price_Array)) { class=class="str">"cmt">// 处理 CopyClose 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyClose: 错误 #%u", __FUNCSIG__, _LastError); class="kw">return(ENTRY_NONE); } d_Actual_Price = da_Price_Array[class="num">0]; } class=class="str">"cmt">// 上方边界: if(go_Channel.i_Highest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_High < d_Actual_Price) { class=class="str">"cmt">// 2nd condition class=class="str">"cmt">// 价格突破了上方边界 if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 价格 (%s) 已经突破了上方边界 (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_High, _Digits)); class="kw">return(ENTRY_SELL); } class=class="str">"cmt">// 下方边界: if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) class=class="str">"cmt">// 第一个条件 if(go_Channel.d_Low > d_Actual_Price) { class=class="str">"cmt">// 第二个条件 class=class="str">"cmt">// 价格突破了下方边界 if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 价格 (%s) 已经突破了下方边界 (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_Low, _Digits)); class="kw">return(ENTRY_BUY); } class=class="str">"cmt">// 如果程序达到了这一行,价格就在范围之内,也就是第二个条件不满足 if(b_Wait_For_Bar_Close) class=class="str">"cmt">// 对于海龟汤升级版 class=class="str">"cmt">// 暂停,直到当前柱关闭 st_Pause_End = go_Tick.time + PeriodSeconds() - go_Tick.time % PeriodSeconds(); class="kw">return(ENTRY_NONE); } enum ENUM_STRATEGY { class=class="str">"cmt">// 策略版本 TS_TURTLE_SOUP, class=class="str">"cmt">// 海龟汤 TS_TURTLE_SOUP_PLIS_1 class=class="str">"cmt">// 海龟汤升级版 }; class=class="str">"cmt">// 自定义设置 class="kw">input ENUM_STRATEGY Turtle_Soup_Type = TS_TURTLE_SOUP; class=class="str">"cmt">// 海龟汤: 策略版本 class="kw">input class="type">uint Turtle_Soup_Period_Length = class="num">20; class=class="str">"cmt">// 海龟汤: 极值点搜索深度 (柱数) class="kw">input class="type">uint Turtle_Soup_Extremum_Offset = class="num">3; class=class="str">"cmt">// 海龟汤: 距离上一个极值点的暂停(柱数) class="kw">input class="type">class="kw">double Turtle_Soup_Entry_Offset = class="num">10; class=class="str">"cmt">// 海龟汤: 进场: 与极值水平的偏移 (点数) class="kw">input class="type">class="kw">double Turtle_Soup_Exit_Offset = class="num">1; class=class="str">"cmt">// 海龟汤: 推出: 与相反极值点的偏移 (点数)
◍ 用于测试交易策略的基础EA交易
在靠近EA交易开头的代码中,我们加入了自定义栏位,在这些栏位之前我们加入了在设置中使用的枚举列表的列表。让我们把设置分成两组 — "策略设置" 和 "仓位的建立和管理". 第一组设置将在编译的时候从信号库文件包含,这样,我们创建了一个文件,在下面的文章中,我们将编程实现书中的其他策略,并且可以替换(或者增加)信号模块,包括所需的自定义设置。 现在我们在 MQL5 标准库文件 开始之前包含代码,用于进行交易操作: 对于这个策略,作者并没有提到任何特定的资金管理或者风险管理技术,所以我们对所有交易使用固定手数。 跟踪止损设置应该输入点数。5位小数报价的引入会对单位的使用造成混淆,所以注意这里的一个点是对应着交易品种价格的最小变化。这就是说,对于5位小数报价,一个点等于 0.00001, 而对于4位小数报价,它等于 0.0001。不要和 pips 混淆 — pips 会忽视报价的真正精确度,总需要把它们转换成4位小数。也就是说,如果交易品种价格变化的最小数是 0.00001, 一个 pip 就等于 10 points; 如果 point 等于 0.0001, point 和 pip 的数值就是相同的。 跟踪止损函数在每个订单时刻都会使用这些设置,而且每天会重新计算成千上万的交易品种的真实价格,尽管这不需要消耗过多的 CPU 资源。在EA交易初始化时重新计算用于输入的数值也许更加正确,并且把它们保存到全局变量中以便今后使用。用于规范化手数的变量也是有相同的原则 — 最小值和最大值的限制, 以及 EA 交易在运行时的改动的步长。这里是全局变量的声明和初始化函数: 请注意, MQL5 标准库中包含了我们需要的这种类型的跟踪止损模块 ( TrailingFixedPips.mqh ), 并且我们可以像其他进行交易操作的类一样,把它包含在代码中。但是这和 EA 交易的特性并不完全符合,所以我们将写自己的跟踪代码并且把它以独立的自定义函数的方式加到EA交易中: 检查是否允许在新的距离中设置止损是包含在独立函数 fb_Is_Acceptable_Distance 中的, 它也可用于验证设置挂单的水平和已建仓位的止损水平。 现在我们继续EA交易代码的主要工作区域,它是由处理新订单时刻到来时间的处理函数调用的 - OnTick。根据策略的规则,如果有已经建立的仓位,EA就不应该搜索新的信号,所以我们开始要做对应的检查。如果已经有了仓位,EA有两种选项: 或者计算和设置新仓位的初始止损水平,或者激活跟踪止损函数,即确定是否应该移动止损,从而进行对应的操作。调用跟踪止损函数很简单,而对于止损水平的计算,我们将使用由用户输入的与极值点 gd_Exit_Offset 偏移点数并把它转换为交易品种的价格。极值点的价格数值可以通过使用标准 MQL5 函数 CopyHigh 或 CopyLow 来寻找。计算所得的水平应该随后使用 fb_Is_Acceptable_Distance 函数来验证,并且使用来自 go_Tick 结构中的当前价格数值。我们将止损买入(BuyStop)和止损卖出(SellStop)订单的计算和验证独立分开: 除了计算新订单时刻的参数,我们还需要更新通道的参数,它们用于信号的侦测。调用对应的 go_Channel 结构的 f_Set 函数只应该在柱关闭之后进行,而这些参数在其余时间中都保持不变。交易机器人还有一个操作与新的一天(柱)开始有关,就是删除不需要的昨天的挂单。Let's program these two actions: 这里使用的 fi_Get_P
「把点数换算成报价再挂跟踪止损」
EA 初始化时先把外部输入的点数参数统一除以 10^_Digits,得到当前交易品种的真实价格偏移。比如黄金 _Digits 通常是 3 或 2,若 Trail_Trigger 输入 500 点,在 XAUUSD 上就会变成 0.5 或 5.0 的价格距离,不转换直接下单会被服务器拒掉。 手数边界也得在 OnInit 里抓一次:SYMBOL_VOLUME_MIN / MAX / STEP 三个值存进全局变量,后面开仓前拿去校验,能避开「无效手数」报错。止损最小距离 SYMBOL_TRADE_STOPS_LEVEL 同样转成价格,后面移动止损前拿来比对。 fb_Trailing_Stop 的逻辑很直接:没仓位就退出;买仓要等当前价相对开仓价拉开超过 d_Trail_Trigger 才激活,且每次只在新价距原 SL 超过 d_Trail_Distance + d_Trail_Step 时才把 SL 向盈利方向推 d_Trail_Distance。卖仓对称处理。 推止损前必须过一遍 fb_Is_Acceptable_Distance,确认新 SL 不满足 broker 的 stop level 限制,否则 PositionModify 会返回错误。外汇和贵金属杠杆高,跟踪止损只是控制回撤的工具,不代表胜率,实盘前请在 MT5 策略测试器用历史数据跑一遍确认参数适配。
gd_Exit_Offset, class=class="str">"cmt">// 退出: 交易品种价格中与极值点的偏移 gd_Trail_Trigger, gd_Trail_Step, gd_Trail_Distance class=class="str">"cmt">// 转换为交易品种价格的跟踪止损参数 ; class="type">MqlTick go_Tick; class=class="str">"cmt">// 最后所知订单时刻的信息 class="type">int OnInit() { class=class="str">"cmt">// 把设置从点数转换为交易品种价格: class="type">class="kw">double d_One_Point_Rate = pow(class="num">10, _Digits); gd_Entry_Offset = Turtle_Soup_Entry_Offset / d_One_Point_Rate; gd_Exit_Offset = Turtle_Soup_Exit_Offset / d_One_Point_Rate; gd_Trail_Trigger = Trail_Trigger / d_One_Point_Rate; gd_Trail_Step = Trail_Step / d_One_Point_Rate; gd_Trail_Distance = Trail_Distance / d_One_Point_Rate; gd_Stop_Level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) / d_One_Point_Rate; class=class="str">"cmt">// 手数限制的初始化: gd_Lot_Min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); gd_Lot_Max = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); gd_Lot_Step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); class="kw">return(INIT_SUCCEEDED); } class="type">bool fb_Trailing_Stop( class=class="str">"cmt">// 当前交易品种仓位的跟踪止损 class="type">class="kw">double d_Trail_Trigger, class=class="str">"cmt">// 启用跟踪止损的距离 class="type">class="kw">double d_Trail_Step, class=class="str">"cmt">// SL 跟踪止损步长 (以交易品种价格计算) class="type">class="kw">double d_Trail_Distance class=class="str">"cmt">// 价格距离止损的最小距离 (在交易品种价格中) ) { if(!PositionSelect(_Symbol)) class="kw">return(class="kw">false); class=class="str">"cmt">// 没有仓位,不需要跟踪止损 class=class="str">"cmt">// 计算新的止损水平的基础数值 - 当前价格数值: class="type">class="kw">double d_New_SL = PositionGetDouble(POSITION_PRICE_CURRENT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { class=class="str">"cmt">// 对于买入仓位 if(d_New_SL - PositionGetDouble(POSITION_PRICE_OPEN) < d_Trail_Trigger) class="kw">return(class="kw">false); class=class="str">"cmt">// 价格没有移动到足够的位置来启用跟踪止损 if(d_New_SL - PositionGetDouble(POSITION_SL) < d_Trail_Distance + d_Trail_Step) class="kw">return(class="kw">false); class=class="str">"cmt">// 价格改动少于设置的跟踪止损步长 d_New_SL -= d_Trail_Distance; class=class="str">"cmt">// 新的止损水平 } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { class=class="str">"cmt">// 对于卖出仓位 if(PositionGetDouble(POSITION_PRICE_OPEN) - d_New_SL < d_Trail_Trigger) class="kw">return(class="kw">false); class=class="str">"cmt">// 价格没有移动到足够的位置来启用跟踪止损 if(PositionGetDouble(POSITION_SL) > class="num">0.0) if(PositionGetDouble(POSITION_SL) - d_New_SL < d_Trail_Distance + d_Trail_Step) class="kw">return(class="kw">false); class=class="str">"cmt">// 价格没有移动到足够的位置来启用跟踪止损 d_New_SL += d_Trail_Distance; class=class="str">"cmt">// New SL level } else class="kw">return(class="kw">false); class=class="str">"cmt">// 服务器设置是否允许在与当前价格这样的距离中设置止损? if(!fb_Is_Acceptable_Distance(d_New_SL, PositionGetDouble(POSITION_PRICE_CURRENT))) class="kw">return(class="kw">false); CTrade Trade; Trade.LogLevel(LOG_LEVEL_ERRORS); class=class="str">"cmt">// 移动止损 Trade.PositionModify(_Symbol, d_New_SL, PositionGetDouble(POSITION_TP)); class="kw">return(true); }