点数图EA少做多赚实战解析
📉

点数图EA少做多赚实战解析

用PnF图表与资金管理构建低频次高回报自动交易

技术分析基础 难度 · 进阶 2026-01-25 6 分钟阅读
#点数图#PnF#MQL5#资金管理

什么是点数图与为什么用它做自动交易

点数图(Point and Figure,简称PnF)是一种古老但高效的图表表示方法,它忽略时间轴,只关注价格的单向变动。当价格反向变动达到预设的“箱体尺寸”时才添加新列,用X表示上涨列、O表示下跌列。与K线不同,PnF能过滤市场噪音,清晰呈现支撑与阻力区域。原文作者Roman Zamozhnyy在其脚本中绘制PnF图,但并未自动化;本文进一步将PnF用于EA决策:顺势在突破阻力时买入,跌破支撑时卖出,并以轴点设止损、趋势线附近追踪止损。

由于不同品种价格差距极大(从几分至数万),作者采用价格对数的点数图,避免手动设置从1到数万的箱体尺寸。PnF数组索引从0开始,最后一列索引为列数减1。若未使用支撑或阻力线,图表中该值设为-10.0(小于价格对数),突破条件即以此为准。理解这一点对编写策略代码至关重要,否则容易出现数组越界或错误触发。

✦ 提示
PnF Chartist脚本已在MQL市场发售,可基于MT4/MT5或雅虎财经数据绘制点数图,适合人工范式搜索,再套用本文EA模板验证。

两种基础策略的逻辑与挂单规则

原文给出两类可自动化交易的假定。策略一:在支撑线以上且高于之前X列价格处挂买单,在阻力线以下且低于之前O列价格处挂卖单,轴点水平使用追随止损;平掉亏损单,让利润增长。策略二:突破阻力线买入,突破支撑线卖出,轴点设止损,趋势线附近追踪止损。两者均强调“少交易、控风险、让利润奔跑”。

在代码实现中,策略一需比较column_count-1与column_count-3列的最大价,确认反转条件;策略二仅用最新列的resist_price与supp_price与实时Ask比较。挂单类型使用ORDER_TYPE_BUY_STOP或SELL_STOP,并在新条件出现时先删除旧单再下单,避免重复或错误方向。

按可接受风险计算交易量的函数

资金管理的核心是:每笔交易亏损风险作为可优化变量(opt_f)。作者用AccountInfoDouble(ACCOUNT_EQUITY)*opt_f得到可接受损失金额,再除以止损距离换算的tick价值,算出手数。这样触发止损时,损失不超出预设百分比,即Ralph Vince所称的资金管理优化。

void PlaceOrder()
  {
   uint digits_2_lot=(uint)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   double trade_risk=AccountInfoDouble(ACCOUNT_EQUITY)*opt_f;
   double one_tick_loss_min_lot=SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE_LOSS)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
   trade_request.magic=magic;
   trade_request.symbol=symbol;
   trade_request.action=TRADE_ACTION_PENDING;
   trade_request.tp=NULL;
   trade_request.comment=NULL;
   trade_request.type_filling=NULL;
   trade_request.stoplimit=NULL;
   trade_request.type_time=NULL;
   trade_request.expiration=NULL;
   if(is_const_lot==true)
     {
      order_vol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
     }
   else
     {
      order_vol=trade_risk/(MathAbs(trade_request.price-trade_request.sl)*MathPow(10,digits_2_lot)*one_tick_loss_min_lot)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
      order_vol=MathMax(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN));
      if(SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT)!=0) order_vol=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT));
      order_vol=NormalizeDouble(order_vol,(int)MathAbs(MathLog10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP))));
     }
   while(order_vol>0)
     {
      trade_request.volume=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX));
      if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
      order_vol=order_vol-SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
     };
   ticket=trade_result.order;
  };
⚠ 常见坑
若is_const_lot为false但price与sl相等会导致除零;测试时部分用户报除数零或数组越界,需确保PnF列已填充且begin_of_test前有足够历史。

优化准则与重新优化时机

只有两种优化准则:给定回报下最小回撤,或给定余额最大回撤。作者选第二种,变量risk即不可接受回撤级(如10%)。OnTester()中若STAT_EQUITY_DDREL_PERCENT>risk*100则返回0剔除,否则返回利润。重新优化不需要固定时间,仅当实盘回撤超risk才做;若系统顺利,不必动。

double OnTester()
  {
   if(TesterStatistics(STAT_EQUITY_DDREL_PERCENT)>(risk*100))
      return(0);
   else
      return(NormalizeDouble(TesterStatistics(STAT_PROFIT),(uint)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  };

在策略测试员“所有品种在市场报价中选择”模式不能优化外部变量,因此作者用枚举SYMBOLS与SelectSymbol()逐一切换品种,并优化箱体尺寸。品种列表含道指成分股与货币对,用户可加自选。

enum SYMBOLS { AA=1, AIG, AXP, BA, C, CAT, DD, DIS, GE, HD, HON, HPQ, IBM, IP, INTC, JNJ, JPM, KO, MCD, MMM, MO, MRK, MSFT, PFE, PG, QQQ, T, SPY, UTX, VZ, WMT, XOM };
void SelectSymbol()
  {
   switch(selected_symbol)
     {
      case 1: symbol="#AA"; break;
      case 2: symbol="#AIG"; break;
      default: symbol="#SPY"; break;
     };
  };

EA骨架:即时价格处理与条件检查

EA在OnTick中仅当新柱完成时重算指标,区分测试/优化模式与实盘:均先判定位是否已开仓,有则移损,无则判开户条件。这样避免每tick重复运算,也贴合PnF基于收盘价重绘的特性。

void OnTick()
  {
   if(IsNewBar()==true)
     {
      RecalcIndicators();
      if((MQLInfoInteger(MQL_TESTER)==true) || (MQLInfoInteger(MQL_OPTIMIZATION)==true))
        {
         if(cur_bar_time_dig[0]>begin_of_test)
           {
            if(PositionSelect(symbol)==true) TrailCondition();
            else TradeCondition();
           }
        }
      else
        {
         if(PositionSelect(symbol)==true) TrailCondition();
         else TradeCondition();
        }
     };
  };

TradeCondition()先清理旧单:若order_col_number不等于column_count且有订单则删除。之后策略一比较前第三列最高价,策略二直接比最新列阻力与Ask。以下为策略二片段:突破阻力则BUY_STOP,跌破支撑则SELL_STOP,并管理已有挂单。

void TradeCondition()
  {
   if(order_col_number!=column_count)
     {
      if(OrdersTotal()>0)
        {
         for(int loc_count_1=0;loc_count_1<OrdersTotal();loc_count_1++)
           {
            ticket=OrderGetTicket(loc_count_1);
            if(!OrderSelect(ticket)) Print("Failed to select order #",ticket);
            if(OrderGetString(ORDER_SYMBOL)==symbol)
              {
               trade_request.order=ticket;
               trade_request.action=TRADE_ACTION_REMOVE;
               if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
              };
           };
         order_col_number=column_count; return;
        }
      else { order_col_number=column_count; return; }
     }
   if(MathPow(10,pnf[column_count-1].resist_price)>SymbolInfoDouble(symbol,SYMBOL_ASK))
     {
      trade_request.price=NormalizeDouble(MathPow(10,pnf[column_count-1].resist_price),digit_2_orders);
      trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-1].resist_price-(reverse-1)*double_box),digit_2_orders);
      trade_request.type=ORDER_TYPE_BUY_STOP;
      if(OrderSelect(ticket)==false) { PlaceOrder(); order_col_number=column_count; }
      else { /* 管理已有单 */ }
      return;
     }
   else if(MathPow(10,pnf[column_count-1].resist_price)<SymbolInfoDouble(symbol,SYMBOL_ASK))
     {
      trade_request.price=NormalizeDouble(MathPow(10,pnf[column_count-1].supp_price),digit_2_orders);
      trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-1].supp_price+(reverse-1)*double_box),digit_2_orders);
      trade_request.type=ORDER_TYPE_SELL_STOP;
      if(OrderSelect(ticket)==false) { PlaceOrder(); order_col_number=column_count; }
     }
  };

回测设置与历史数据长度要求

EA至少需要几列PnF分析。若箱体尺寸≥50点,一年历史常不足一列。作者测箱体100点时,设测试开始2009.01.01,参数开始2012.01.01。Trade minimum lot?设false时手数按余额与Risk per Trade %(例1%)计算;Max Drawdown %作优化准则。仅优化品种与箱体尺寸两变量。

附件提供symbol_list_1/2.mqh两套品种。基于策略一箱体10多货币对测试,EURGBP、USDCHF等少数盈利;汇总显示股票箱体100时81笔交易两年本金增44%,回撤略超2%;若风险4%则增177%。证明低频交易反易保住存款。

✦ 实操建议
选经纪商提供300-400个低相关品种(股、期),比仅20-40个标准集合更安全;股票表现常优于货币对。

结论与给中文交易者的启示

作者戏言“华尔街傻瓜相信你总在交易”,而数据表明存款增长靠较少交易。不要在高位追买、低位杀卖,不妄想暴富。上述策略可改造成回撤不超10%下扩回报。最好人工用PnF Chartist搜范式,再用EA模板证伪。本文代码见原文附件:strategy_one.mq5、strategy_two.mq5等,地址 https://www.mql5.com/zh/articles/910 。

对比常规均线EA频繁进出,PnF EA以价格结构为门槛,天然过滤震荡;对比固定手数马丁,它按权益百分比风险,生存力更强。但需警惕点数图重绘延迟与品种流动性。建议先在MT5策略测试器用中文环境复现,再上模拟盘。

常见问题

用权益乘可接受风险百分比opt_f得金额,除以(入场价-止损价)*10^小数位*tick价值损失,再乘最小步长并规范手数,代码见PlaceOrder函数。
箱体尺寸≥50点时一年常不足一列PnF,分析需多列;作者测100点箱体设测试始2009年参数始2012年,确保列数足够。
用枚举SYMBOLS加SelectSymbol()在OnInit切换品种,仅优化箱体尺寸等,不在“所有品种”模式优化外部变量。
若STAT_EQUITY_DDREL_PERCENT>risk*100返回0剔除,否则返回STAT_PROFIT,risk即最大可接受回撤百分比。
TradeCondition中order_col_number不等column_count时遍历OrdersTotal删同品种单,再按新条件挂BUY/SELL STOP。