自动交易的传说: 是少或多?·综合运用
(3/3)·从手工猜突破到脚本自动下单,这篇把前两篇的原理压进一套能实盘试错的交易机器人
◍ 点数图挂单的买卖切换与纠错
在点数图(PNF)策略里,挂单方向由最新列的阻力/支撑换算价与实时卖价的关系决定。若 10 的 resist_price 次幂不小于 SYMBOL_ASK,则按 BUY STOP 处理;反之若小于 ASK,则走 SELL STOP 分支。 trade_request.price 与 sl 都用 NormalizeDouble 配合 MathPow(10, …) 算出,digit_2_orders 控制小数位。以买分支为例,sl 取 resist_price 减去 (reverse-1)*double_box 后的幂值,卖分支则加回,框数偏移直接决定止损距离。 每轮先 OrderSelect(ticket) 看旧挂单在不在。不在就直接 PlaceOrder() 并把 order_col_number 设为当前 column_count;在的话再比对类型与开价——买分支里若旧单是 SELL_STOP,或虽是 BUY_STOP 但价格不等于请求价,就先 TRADE_ACTION_REMOVE 撤掉,再 PlaceOrder() 重挂。 外汇与贵金属杠杆高,这种自动撤补逻辑可能在快速波动中连续触发手续费与滑点,实盘前务必在 MT5 策略测试器用历史数据跑一遍 reverse 与 double_box 组合。
trade_request.price=NormalizeDouble(MathPow(class="num">10,pnf[column_count-class="num">1].resist_price),digit_2_orders); trade_request.sl=NormalizeDouble(MathPow(class="num">10,pnf[column_count-class="num">1].resist_price-(reverse-class="num">1)*double_box),digit_2_orders); trade_request.type=ORDER_TYPE_BUY_STOP; if(OrderSelect(ticket)==class="kw">false) class=class="str">"cmt">//--- No pending orders - place an order { PlaceOrder(); order_col_number=column_count; } else class=class="str">"cmt">//--- or there is a pending order { class=class="str">"cmt">//--- what is the type and price of the pending order? if((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP) || ((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP) && (OrderGetDouble(ORDER_PRICE_OPEN)!=trade_request.price))) { class=class="str">"cmt">//--- The wrong type or the price differs - close the order trade_request.order=ticket; trade_request.action=TRADE_ACTION_REMOVE; if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order); class=class="str">"cmt">//--- open with the desired price PlaceOrder(); order_col_number=column_count; }; }; class="kw">return; } else if(MathPow(class="num">10,pnf[column_count-class="num">1].resist_price)<SymbolInfoDouble(symbol,SYMBOL_ASK)) { class=class="str">"cmt">//--- Conditions for SELL met; let&class="macro">#x27;s see if there are any pending Sell orders for the symbol with the price we need? trade_request.price=NormalizeDouble(MathPow(class="num">10,pnf[column_count-class="num">1].supp_price),digit_2_orders); trade_request.sl=NormalizeDouble(MathPow(class="num">10,pnf[column_count-class="num">1].supp_price+(reverse-class="num">1)*double_box),digit_2_orders); trade_request.type=ORDER_TYPE_SELL_STOP; if(OrderSelect(ticket)==class="kw">false) class=class="str">"cmt">//--- No pending orders - place an order { PlaceOrder(); order_col_number=column_count; } else class=class="str">"cmt">//--- If there is a pending order { class=class="str">"cmt">//--- what is the type and price of the pending order? if((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP) || ((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP) && (OrderGetDouble(ORDER_PRICE_OPEN)!=trade_request.price))) {
挂单失配时的撤单重发与止损跟随
当检测到挂单类型不对或价格偏离预期,代码先走撤单逻辑:把 ticket 填回请求结构,action 设为 TRADE_ACTION_REMOVE,调用 OrderSend 删单;若发送失败就打印单号,随后立即用 PlaceOrder() 以目标价格重新挂出,并把 order_col_number 同步为当前 column_count。这一套在 MT5 里跑起来,能避免残单占用仓位额度。 TrailCondition() 则负责移动止损。多头仓用 SYMBOL_ASK 与 10 的(max_column_price - reverse*double_box)次幂取大值,空头仓用 SYMBOL_BID 与对应次幂取小值,再 NormalizeDouble 到 digit_2_orders 精度。只有当新 SL 与 POSITION_SL 不一致时才触发 PlaceTrailOrder(),减少无谓交易指令。 外汇与贵金属杠杆高,这类自动撤重与追损逻辑可能在快速波动中连续触发,实盘前务必用策略测试器按 Tick 模式回测,观察滑点下的重发频率。
class=class="str">"cmt">//--- The wrong type or the price differs - close the order trade_request.order=ticket; trade_request.action=TRADE_ACTION_REMOVE; if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order); class=class="str">"cmt">//--- open with the desired price PlaceOrder(); order_col_number=column_count; }; }; class="kw">return; } else class="kw">return; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The function checks conditions for moving Stop Loss | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void TrailCondition() { if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) trade_request.sl=NormalizeDouble(MathMax(SymbolInfoDouble(symbol,SYMBOL_ASK),MathPow(class="num">10,pnf[column_count-class="num">1].max_column_price-reverse*double_box)),digit_2_orders); else trade_request.sl=NormalizeDouble(MathMin(SymbolInfoDouble(symbol,SYMBOL_BID),MathPow(class="num">10,pnf[column_count-class="num">1].min_column_price+reverse*double_box)),digit_2_orders); if(PositionGetDouble(POSITION_SL)!=trade_request.sl) PlaceTrailOrder(); };
「回测数据暴露的品种与箱体规律」
两套预设品种列表已分别装入 symbol_list_1.mhq 与 symbol_list_2.mhq,覆盖外汇直盘交叉盘及道琼斯股票期货。EA 至少需要数根完整价格柱做分析,箱体设到 50 点以上时,一年历史往往凑不出一根柱;本例箱体 100 点,设置页测试起点拉到 2009.01.01,参数页实跑起点设 2012.01.01,优化窗口取 2012–2013。 手数逻辑由余额与 Risk per Trade 驱动(本例每笔 1%,可优化),Max Drawdown 作为 OnTester() 的优化准则,全文仅拿品种和箱体点数两个变量开跑。EURUSD 上即时报价覆盖最好,但 10 点箱体的 2012–2013 横评里它净亏 1058.85,获利因子 0.65;同表 USDCHF 净赚 2166.53、夏普 2.40,AUDCAD 赚 886.56、回撤仅 5.76%。 汇总表更刺眼:策略1股票期货箱体 100,仅 81 笔交易,净值回撤略超 2%,本金增长约 44%;把可承受回撤放宽到 10%、单笔风险提到 4%,两年本金增长倾向冲到 177%(年化约 90%)。外汇 10 点箱体 1659 笔交易反而净亏 10213.52——交易频次和收益在这里是倒挂的。 外汇与贵金属属高杠杆品种,回测里的正向结果只是特定参数区间的概率性表现,实盘滑点、点差扩大都会啃掉样本外的空间,别把 44% 或 177% 当成可复制的承诺。
◍ 回撤可控时再谈放大收益
原文提到的基准策略在回撤不超过10%的前提下,存在通过参数修改放大回报的空间,但这不意味着能无脑加仓。频繁交易只会增加滑点与手续费损耗,对散户存款并不友好。 品种数量比多数人想的重要。如果券商只给20到40个标准外汇品种,相关性很容易把风险叠在一起;更稳妥的做法是找能提供300到400个标的(股票、期货都算)的券商,且标的最好彼此弱关联。 一个可验证的现象是:同一套规则跑股票常常比跑货币对更好看,外汇和贵金属本身高杠杆高波动,实盘前请在MT5用策略测试器先跑一遍样本外数据。
把范式交给眼睛和 EA 去碰
作者上架的 PnF Chartist 脚本能吃 MT4、MT5 或雅虎财经的报价,在文本文件里画点数图。它的定位不是自动找信号,而是把价格范式摊开给人脑扫——原文直言没有比交易者大脑更好的测试/优化器。 看到疑似范式后,直接套前文给的 EA 模板(strategy_one.mq5 与 strategy_two.mq5,分别约 37.87 KB 与 37.39 KB)去回测验证。附件里还带了 symbol_list_1.mqh、symbol_list_2.mqh 做品种清单,省去手填代码。 有用户反馈在测试模式下两版 EA 都崩过:strategy_two.mq5 第 348 行报除零,strategy_one.mq5 第 438 行报数组越界。外汇与贵金属杠杆高、滑点跳空频繁,这类历史bug在实盘前必须自己修掉再跑。 点数是人眼先筛、机器后证,别把脚本当黑箱。开 MT5 把附件丢进 MQL5/Files 跑一遍,比信任何'现成策略'都实在。