点数图EA少做多赚实战解析
用PnF图表与资金管理构建低频次高回报自动交易
什么是点数图与为什么用它做自动交易
点数图(Point and Figure,简称PnF)是一种古老但高效的图表表示方法,它忽略时间轴,只关注价格的单向变动。当价格反向变动达到预设的“箱体尺寸”时才添加新列,用X表示上涨列、O表示下跌列。与K线不同,PnF能过滤市场噪音,清晰呈现支撑与阻力区域。原文作者Roman Zamozhnyy在其脚本中绘制PnF图,但并未自动化;本文进一步将PnF用于EA决策:顺势在突破阻力时买入,跌破支撑时卖出,并以轴点设止损、趋势线附近追踪止损。
由于不同品种价格差距极大(从几分至数万),作者采用价格对数的点数图,避免手动设置从1到数万的箱体尺寸。PnF数组索引从0开始,最后一列索引为列数减1。若未使用支撑或阻力线,图表中该值设为-10.0(小于价格对数),突破条件即以此为准。理解这一点对编写策略代码至关重要,否则容易出现数组越界或错误触发。
两种基础策略的逻辑与挂单规则
原文给出两类可自动化交易的假定。策略一:在支撑线以上且高于之前X列价格处挂买单,在阻力线以下且低于之前O列价格处挂卖单,轴点水平使用追随止损;平掉亏损单,让利润增长。策略二:突破阻力线买入,突破支撑线卖出,轴点设止损,趋势线附近追踪止损。两者均强调“少交易、控风险、让利润奔跑”。
在代码实现中,策略一需比较column_count-1与column_count-3列的最大价,确认反转条件;策略二仅用最新列的resist_price与supp_price与实时Ask比较。挂单类型使用ORDER_TYPE_BUY_STOP或SELL_STOP,并在新条件出现时先删除旧单再下单,避免重复或错误方向。
- 顺势交易:牛势买、熊势卖
- 入场前定义止损,用挂单限定成交价
- 回撤超10%则重新优化,否则放任运行
- 优先选低相关性的多品种分散存款风险
按可接受风险计算交易量的函数
资金管理的核心是:每笔交易亏损风险作为可优化变量(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;
};
优化准则与重新优化时机
只有两种优化准则:给定回报下最小回撤,或给定余额最大回撤。作者选第二种,变量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%。证明低频交易反易保住存款。
结论与给中文交易者的启示
作者戏言“华尔街傻瓜相信你总在交易”,而数据表明存款增长靠较少交易。不要在高位追买、低位杀卖,不妄想暴富。上述策略可改造成回撤不超10%下扩回报。最好人工用PnF Chartist搜范式,再用EA模板证伪。本文代码见原文附件:strategy_one.mq5、strategy_two.mq5等,地址 https://www.mql5.com/zh/articles/910 。
对比常规均线EA频繁进出,PnF EA以价格结构为门槛,天然过滤震荡;对比固定手数马丁,它按权益百分比风险,生存力更强。但需警惕点数图重绘延迟与品种流动性。建议先在MT5策略测试器用中文环境复现,再上模拟盘。