实用且奇特的自动交易技术(基础篇)
部分平仓与混合手数的实盘算法骨架
在 MT5 自动交易里,有一类做法不靠预测方向,而是用最后一根柱线的走势去切分平仓:只平掉持仓的一部分,而不是一次性清仓。这种部分平仓算法配合混合手数变化,能在不破坏均摊成本结构的前提下,把浮亏摊薄的时间拉长。 均摊原理在这里不是简单马丁加码,而是让新开手数随账户状态浮动。直接看交易稳固性,有两个线索:余额曲线是否平滑、净值回撤是否可控;间接迹象则是订单成交间隔与滑点分布是否稳定。 一个可验证现象:在 2021 年 5 月那轮波动中,这类 EA 的公开示例帖被查看 7118 次、评论 27 条,说明散户对「非全平」的平仓逻辑有真实需求。外汇与贵金属杠杆高,任何自动切仓策略都可能放大回撤,上机前先用策略测试器跑历史数据。
◍ 为什么只聊非标准实战技法
很多交易者以为盈利方法都藏在那些被广泛转发的经典教程里。其实 MT5 社区里能查到的标准策略资料已经够多,再复述一遍既没信息增量,也帮不了你当下盯盘。 这篇系列不打算走复读路线,而是把若干种实用且偏非标准的技法拢到一起。它们有的你可能在别处扫过一眼,有的则很少被拿来做可执行拆解。 我会挑出最值得动手验证的几类,说清它们为什么在价格行为里有存在价值,并直接给出能在 MT5 里跑起来的适用方式。外汇与贵金属波动杠杆高,任何技法都只是概率优势,不是确定性出路。
「用幂函数来吃掉点差:最后一根柱线驱动的部分平仓」
横盘是市场的常态结构,一段上行脉动之后价格回滚到波浪起点一半位置的概率通常大于 50%,而延续的概率不足 50%。若一次性全平,往往错过后续幅度;分步骤部分平仓,则能在不确定未来波浪性质时,先锁住点差成本甚至转亏为盈。外汇与贵金属杠杆高、滑点跳空频繁,这种机制只是风控工具,不承诺任何方向收益。 核心思路是把「前一根柱线向盈利方向走出的点数 X」映射成「本次应平手数 Lc(X)」。原文推导出一个幂函数:Lc(X) = StartLotsToOnePoint * Pow( X , ln(EndLotsToOnePoint / StartLotsToOnePoint) / ln(PointsForEndLots) )。指数 D 的可调区间覆盖了线性(D=1)、陡增(D>1,假定低振幅波浪为主)与平缓(D<1,押注高振幅波浪)三种姿态,比写死线性公式灵活得多。 MQL5 没有任意底数对数,只能用自然对数 MathLog 拼出 D。下面这段代码就是该函数在 EA 里的落地,注意 MathPow 与两次 MathLog 的嵌套正是上面代数结果的直译: [CODE] double CalcCloseLots(double orderlots0,double X) { double functionvalue; double correctedlots; if ( X < 0.0 ) return 0.0; functionvalue=StartLotsToOnePoint*MathPow(X ,MathLog(EndLotsToOnePoint/StartLotsToOnePoint)/MathLog(PointsForEndLots)); correctedlots=GetLotAniError(functionvalue); if ( correctedlots > orderlots0 ) return orderlots0; else return correctedlots; } void PartialCloseType()// close order partially { bool ord; double ValidLot; MqlTick TickS; SymbolInfoTick(_Symbol,TickS); for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == _Symbol ) { if ( OrderType() == OP_BUY ) { 逐行拆解:CalcCloseLots 接收「当前持仓手数 orderlots0」与「脉动点数 X」;X 为负直接返回 0 不平;functionvalue 就是上文幂函数的数值实现;GetLotAniError 把结果矫正到经纪商允许的最小手数步长;若算出来比剩余持仓还大,就全平剩余,否则按算值平。PartialCloseType 负责遍历当前品种同魔法码持仓,TickS 取实时报价,OrderSelect 按位置选单,遇多单进入分支——后面接具体平仓指令即可。 回测时把点差设成 1 点、随机方向开仓,EA 在 MT4 测试器里能直观看到部分平仓如何覆盖成本;换到 MT5 实盘前必须重校手数函数与错误分支,贵金属和外汇品种点差扩大后原参数会失真。
class="type">class="kw">double CalcCloseLots(class="type">class="kw">double orderlots0,class="type">class="kw">double X) { class="type">class="kw">double functionvalue; class="type">class="kw">double correctedlots; if ( X < class="num">0.0 ) class="kw">return class="num">0.0; functionvalue=StartLotsToOnePoint*MathPow(X ,MathLog(EndLotsToOnePoint/StartLotsToOnePoint)/MathLog(PointsForEndLots)); correctedlots=GetLotAniError(functionvalue); if ( correctedlots > orderlots0 ) class="kw">return orderlots0; else class="kw">return correctedlots; } class="type">void PartialCloseType()class=class="str">"cmt">// close order partially { class="type">bool ord; class="type">class="kw">double ValidLot; class="type">MqlTick TickS; SymbolInfoTick(_Symbol,TickS); for ( class="type">int i=class="num">0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == _Symbol ) { if ( OrderType() == OP_BUY ) {
按跳空点数反推可平仓位
这段逻辑出现在遍历持仓订单的循环里,核心是用相邻两根 K 线的开盘价差来决定平仓手数。CalcCloseLots 接收当前订单手数与跳空点数,算出 ValidLot,只有大于 0.0 才真正发平仓指令。 对于多单,取 Open[0]-Open[1] 除以 _Point 得到向上跳空点数;空单则取 Open[1]-Open[0],对应向下跳空。OrderClose 里多单用 TickS.bid、空单用 TickS.ask,滑点取 SlippageMaxClose 的绝对值,颜色标记 Green / Red 仅用于日志可视化。 在 MT5 实测时,若上一根到当前根开盘跳空不足 1 个点,ValidLot 大概率返回 0.0,订单不会被触碰——这意味着策略只在明显跳空时减仓,普通行情下保持原仓。外汇与贵金属跳空伴随隔夜流动性断裂,属高风险场景,实际滑点可能远超 SlippageMaxClose 设定值。
ValidLot=CalcCloseLots(OrderLots(),(Open[class="num">0]-Open[class="num">1])/_Point); if ( ValidLot > class="num">0.0 ) ord=OrderClose(OrderTicket(),ValidLot,TickS.bid,MathAbs(SlippageMaxClose),Green); } if ( OrderType() == OP_SELL ) { ValidLot=CalcCloseLots(OrderLots(),(Open[class="num">1]-Open[class="num">0])/_Point); if ( ValidLot > class="num">0.0 ) ord=OrderClose(OrderTicket(),ValidLot,TickS.ask,MathAbs(SlippageMaxClose),Red); } class="kw">break;
◍ 用混合手数把零期望信号掰成正期望
把马丁格尔和逆马丁格尔揉在一起,核心前提只有一条:价格走势里得有大量低振幅波浪。信号本身若与点差无关、且等手数测试下数学期望接近零,才有改造空间;外汇和贵金属杠杆高,账户最大持仓会卡死无限加仓,所以手数必须困在 MinLot 到 MaxLot 的走廊里。 逻辑落点是按余额线波浪方向调仓:上升段减仓(逆马丁),下降段加仓(正马丁)。当最新手数 L 在中值 MiddleLot 之上且上一笔亏损,手数慢速往上顶;L 在中值之下且盈利,手数慢速往下压。越贴近边界,反向推力越强,把平均手数拽回中线附近,从而在小幅波动中让资金曲线净斜率转正。 回测里用依次开买、卖、买的信号做演示:固定手数那版权益曲线在原始信号波动最小时几乎走平,混合手数版盈利因子出现改善;一旦遇上大浪,这套机制会直接扛不住。 下面这段 MQL4 风格函数可在挂 MT4Orders 库后于 MT5 跑通,黄色高亮即外部输入参数: [CODE] double CalcMultiplierLot() { bool ord; double templot=(MaxLot+MinLot)/2.0; for ( int i=OrdersHistoryTotal()-1; i>=0; i-- ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_HISTORY ); if ( ord && OrderSymbol() == CurrentSymbol && OrderMagicNumber() == MagicF ) { double PointsProfit=(OrderProfit()+OrderCommission()+OrderSwap())/(OrderLots()*MarketInfo(CurrentSymbol,MODE_TICKVALUE)); if ( OrderLots() >= (MaxLot+MinLot)/2.0 ) { if ( PointsProfit < 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))*((MaxLot-OrderLots())/((MaxLot-MinLot)/2.0)); if ( PointsProfit > 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))/((MaxLot-OrderLots())/((MaxLot-MinLot)/2.0)) ; if ( PointsProfit == 0.0 ) templot=OrderLots(); break; 手数高于中线时的分支:templot 先取中值,再从历史订单倒序找本品种本魔术码最后一笔。PointsProfit 把盈亏加佣金隔夜费折算成点数;L≥中值且亏损,按 (MaxLot-L)/半宽 慢速加仓;L≥中值且盈利,除以该比值加速减仓;平盘则手数不动,随后 break 退出。 开 MT5 把 MaxLot、MinLot、LotForMultiplier、ProfitForMultiplier 四个量设窄一点,先丢 EURUSD 五分钟复盘,看小波浪段权益线是否翘头,再决定是否上实盘——贵金属跳空大浪可能瞬间打穿走廊。
class="type">class="kw">double CalcMultiplierLot() { class="type">bool ord; class="type">class="kw">double templot=(MaxLot+MinLot)/class="num">2.0; for ( class="type">int i=OrdersHistoryTotal()-class="num">1; i>=class="num">0; i-- ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_HISTORY ); if ( ord && OrderSymbol() == CurrentSymbol && OrderMagicNumber() == MagicF ) { class="type">class="kw">double PointsProfit=(OrderProfit()+OrderCommission()+OrderSwap())/(OrderLots()*MarketInfo(CurrentSymbol,MODE_TICKVALUE)); if ( OrderLots() >= (MaxLot+MinLot)/class="num">2.0 ) { if ( PointsProfit < class="num">0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))*((MaxLot-OrderLots())/((MaxLot-MinLot)/class="num">2.0)); if ( PointsProfit > class="num">0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))/((MaxLot-OrderLots())/((MaxLot-MinLot)/class="num">2.0)) ; if ( PointsProfit == class="num">0.0 ) templot=OrderLots(); class="kw">break;