EA 交易运行期间平衡曲线斜率的控制·综合运用
◍ 用回归斜率读出结余曲线走向
TBalanceSlope 类继承自 TBalanceHistory,专门处理账户结余(balance)曲线的斜率计算。它不重新造轮子,而是复用基类的历史交易数据数组,只在上面叠加一套线性回归逻辑。 核心思路是:取结余曲线上最近 N 笔交易对应的点,用最小二乘法拟合出 A*x + B 这条直线,斜率 A 就是结余曲线的倾斜程度。A 为正且越大,说明近期结余曲线向上走的概率偏高;A 接近 0 或为负,则走平或向下倾向更强。外汇和贵金属杠杆高,曲线斜率仅反映历史样本,不代表后续必然延续。 CalcSlope 是主要对外方法,它先调用基类 GetTradeResultsArray 取最后 N 点,点数不足就跳过计算,够数则交给 CalcLR 算回归。CalcLR 里有个硬约束:数组长度小于 2 直接 return,因为单点无法拟线。 下面这段是类的声明与 CalcLR 实现,逐行拆一下关键处:class TBalanceSlope : public TBalanceHistory —— 公有继承,基类数据方法全拿来用。current_slope 存当前斜率角度,slope_count_points 是参与计算的交易笔数,由 SetSlopePoints 设定。LR_koeff_A/B 即回归方程 y=Ax+B 的两个系数,LR_points_array 存拟合线 Y 值,注释写明是给以后扩展用,当前并未直接参与斜率返回。 CalcLR 中 mo_X/mo_Y 先求 X、Y 均值;var_0 累加 (X-mo_X)*(Y-mo_Y),var_1 累加 (X-mo_X)^2,这就是最小二乘分子分母。若 var_1 为 0(所有 X 相同)则 A 强制 0,避免除零;B = mo_Y - A*mo_X。最后 ArrayResize 后把每条拟合 Y 写进 LR_points_array。你在 MT5 里接上自己账户历史,改 slope_count_points 从 10 调到 30,就能直观比对短中长期结余斜率的差异。
<span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">// 结余曲线操作:</span> <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="keyword">class</span> TBalanceSlope : <span class="keyword">class="kw">public</span> TBalanceHistory { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">class="kw">double</span> current_slope; <span class="comment">class=class="str">"cmt">// 结余曲线的斜率的当前角度</span> <span class="keyword">class="type">int</span> slope_count_points; <span class="comment">class=class="str">"cmt">// 计算斜率角度的点数 ( 交易 )</span> <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">class="kw">double</span> LR_koeff_A, LR_koeff_B; <span class="comment">class=class="str">"cmt">// 直线回归方程的比率</span> <span class="keyword">class="type">class="kw">double</span> LR_points_array[ ]; <span class="comment">class=class="str">"cmt">// 直线回归方程点的数组</span> <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">void</span> CalcLR( <span class="keyword">class="type">class="kw">double</span>& X[ ], <span class="keyword">class="type">class="kw">double</span>& Y[ ] ); <span class="comment">class=class="str">"cmt">// 计算直线回归方程</span> <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">void</span> SetSlopePoints( <span class="keyword">class="type">int </span>_number ); class=class="str">"cmt">// 设置计算斜率角度的点数 <span class="keyword">class="type">class="kw">double</span> CalcSlope( ); <span class="comment">class=class="str">"cmt">// 计算斜率角度</span> <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">void</span> TBalanceSlope( ); <span class="comment">class=class="str">"cmt">// 构造器</span> <span class="keyword">class="type">void</span> ~TBalanceSlope( ); <span class="comment">class=class="str">"cmt">// 析构器</span> }; <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">// 计算直线回归方程式:</span> <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">// 输入参数:</span> <span class="comment">class=class="str">"cmt">// X[ ] - X轴数列值;</span> <span class="comment">class=class="str">"cmt">// Y[ ] - Y轴数量值;</span> <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="keyword">class="type">void</span> TBalanceSlope::CalcLR( <span class="keyword">class="type">class="kw">double</span>& X[ ], <span class="keyword">class="type">class="kw">double</span>& Y[ ] ) { <span class="keyword">class="type">class="kw">double</span> mo_X = <span class="number">class="num">0</span>, mo_Y = <span class="number">class="num">0</span>, var_0 = <span class="number">class="num">0</span>, var_1 = <span class="number">class="num">0</span>; <span class="keyword">class="type">int</span> i; <span class="keyword">class="type">int</span> size = <span class="functions">ArraySize</span>( X ); <span class="keyword">class="type">class="kw">double</span> nmb = ( <span class="keyword">class="type">class="kw">double</span> )size; <span class="comment">class=class="str">"cmt">// 如果点数小于 class="num">2, 则曲线不可计算:</span> <span class="keyword">if</span>( size < <span class="number">class="num">2</span> ) { <span class="keyword">class="kw">return</span>; } <span class="keyword">for</span>( i = <span class="number">class="num">0</span>; i < size; i++ ) { mo_X += X[ i ]; mo_Y += Y[ i ]; } mo_X /= nmb; mo_Y /= nmb; <span class="keyword">for</span>( i = <span class="number">class="num">0</span>; i < size; i++ ) { var_0 += ( X[ i ] - mo_X ) * ( Y[ i ] - mo_Y ); var_1 += ( X[ i ] - mo_X ) * ( X[ i ] - mo_X ); } <span class="comment">class=class="str">"cmt">// A 值的系数:</span> <span class="keyword">if</span>( var_1 != <span class="number">class="num">0.0</span> ) { LR_koeff_A = var_0 / var_1; } <span class="keyword">else</span> { LR_koeff_A = <span class="number">class="num">0.0</span>; } <span class="comment">class=class="str">"cmt">// B 值的系数:</span> LR_koeff_B = mo_Y - LR_koeff_A * mo_X; <span class="comment">class=class="str">"cmt">// 填充附在回归线上的点数组:</span> <span class="functions">ArrayResize</span>( <span class="keyword"></span>LR_points_array, size ); <span class="keyword">for</span>( i = <span class="number">class="num">0</span>; i < size; i++ ) { LR_points_array[ i ] = LR_koeff_A * X[ i ] + LR_koeff_B; } } <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span>
从成交序列里抠出权益斜率
想在 MT5 里把账户权益的“走陡还是走平”量化出来,核心不是看余额曲线图,而是用历史交易结果做线性回归。下面这段 MQL5 方法只取最近 slope_count_points 笔成交,样本不够就直接返回 0.0,避免用太少数据硬算斜率骗自己。 代码里 GetTradeResultsArray 先把每笔盈亏按时间塞进数组,若实际笔数 nmb 小于设定点数,函数立刻 return(0.0)——这是防小样本幻觉的关键判断。 随后 CalcLR 用 last_datetime_array 和 last_result_array 跑最小二乘,回归系数 LR_koeff_A 就是当前斜率 current_slope。正数代表近期权益倾向向上,负数则向下,但外汇与贵金属杠杆高、回撤凶,斜率仅反映已发生样本,不代表后续必延续。 实操上把 slope_count_points 设为 20~30 之间去 MT5 回测自己信号源,能快速识别“最近是不是在走运”还是“在缓慢失血”。
class="type">class="kw">double TBalanceSlope::CalcSlope( ) { class=class="str">"cmt">// 从历史交易中取得交易结果: class="type">int nmb = GetTradeResultsArray( slope_count_points ); if( nmb < slope_count_points ) { class="kw">return( class="num">0.0 ); } class=class="str">"cmt">// 以最后交易结果计算回归线: CalcLR( last_datetime_array, last_result_array ); current_slope = LR_koeff_A; class="kw">return( current_slope ); }
「用平衡曲线坡度倒推下单手数」
TBalanceSlopeControl 派生自 TBalanceSlope,本身不画曲线,只干一件事:按当前平衡曲线的坡度算出这一笔该下多少有效交易量。它把坡度分成几段,对应不同的手数档位,EA 刚加载、真实成交笔数还没攒够 SlopePoints 时,一律只丢最小手数——因为这时候策略能不能盈利还是未知。 控制特性有四种可选:STEP_WITH_HYSTERESIS(带迟滞的步进)、STEP_WITHOUT_HYSTERESIS(不带迟滞的步进)、LINEAR(线性)、NON_LINEAR(本版未实现)。带迟滞时要给全 _min_slope、_max_slope、_centr_slope 三个坡度阈值;若选不带迟滞步进,切换角度只认 _centr_slope,其余两个被忽略,这种模式下必须进 MT5 策略测试器对该参数做优化。 CalcTradeLots 是入口,内部先算坡度,再判断真实成交数是否小于 SlopePoints,不足就返回最小手数。中间手数由受保护的 CalcIntermediateLots 按坡度在最小和最大之间插值,外部不用管。 下面这段类声明和核心判断,直接贴进 MT5 的 MQH 就能看到字段映射:min_slope 对应拒绝档、max_slope 对应正常档、centr_slope 管无迟滞切换;LotsState 的三个枚举值把交易模式钉死。外汇和贵金属波动大、杠杆高,这类按坡度调仓的逻辑回测时务必用 tick 级数据,实盘前先跑至少 3 个月历史。
<span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">// 管理结余曲线的倾斜率:</span> <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="keyword">enum</span> LotsState { LOTS_NORMAL = <span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">// 正常交易量的交易模式</span> LOTS_REJECTED = -<span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">// 低交易量的交易模式</span> LOTS_INTERMEDIATE = <span class="number">class="num">0</span>, <span class="comment">class=class="str">"cmt">// 中间交易量的交易模式</span> }; <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="keyword">class</span> TBalanceSlopeControl : <span class="keyword">class="kw">public</span> TBalanceSlope { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">class="kw">double</span> min_slope; <span class="comment">class=class="str">"cmt">// 对应交易量拒绝模式的倾角</span> <span class="keyword">class="type">class="kw">double</span> max_slope; <span class="comment">class=class="str">"cmt">// 对应交易量正常模式的倾角</span> <span class="keyword">class="type">class="kw">double</span> centr_slope; <span class="comment">class=class="str">"cmt">// 对应交易量无迟滞切换模式的倾角</span> <span class="keyword">class="kw">private</span>: ControlType control_type; <span class="comment">class=class="str">"cmt">// 调节功能的类型</span> <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">class="kw">double</span> rejected_lots; <span class="comment">class=class="str">"cmt">// 交易量拒绝模式</span> <span class="keyword">class="type">class="kw">double</span> normal_lots; <span class="comment">class=class="str">"cmt">// 交易量正常模式</span> <span class="keyword">class="type">class="kw">double</span> intermed_lots; <span class="comment">class=class="str">"cmt">// 交易量中间模式</span> <span class="keyword">class="kw">private</span>: LotsState current_lots_state; <span class="comment">class=class="str">"cmt">// 当前交易量模式</span> <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">void</span> SetControlType( ControlType _control ); <span class="comment">class=class="str">"cmt">// 设置调节特征类型</span> <span class="keyword">class="type">void</span> SetControlParams( <span class="keyword">class="type">class="kw">double</span> _min_slope, <span class="keyword">class="type">class="kw">double</span> _max_slope, <span class="keyword">class="type">class="kw">double</span> _centr_slope ); <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">class="kw">double</span> CalcTradeLots( <span class="keyword">class="type">class="kw">double</span> _min_lots, <span class="keyword">class="type">class="kw">double</span> _max_lots ); <span class="comment">class=class="str">"cmt">// 获取交易量</span> <span class="keyword">class="kw">protected</span>: <span class="keyword">class="type">class="kw">double</span> CalcIntermediateLots( <span class="keyword">class="type">class="kw">double</span> _min_lots, <span class="keyword">class="type">class="kw">double</span> _max_lots, <span class="keyword">class="type">class="kw">double</span> _slope ); <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">void</span> TBalanceSlopeControl( ); <span class="comment">class=class="str">"cmt">// 构造器</span> <span class="keyword">class="type">void</span> ~TBalanceSlopeControl( ); <span class="comment">class=class="str">"cmt">// 析构器</span> }; <span class="keyword">class="type">void</span> SetControlType( ControlType _control ); <span class="comment">class=class="str">"cmt">// 设置调节特征类型</span> <span class="keyword">class="type">void</span> SetControlParams( <span class="keyword">class="type">class="kw">double</span> _min_slope, <span class="keyword">class="type">class="kw">double</span> _max_slope, <span class="keyword">class="type">class="kw">double</span> _centr_slope ); <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">// 获得交易量:</span> <span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span> <span class="keyword">class="type">class="kw">double</span> TBalanceSlopeControl::CalcTradeLots( <span class="keyword">class="type">class="kw">double</span> _min_lots, <span class="keyword">class="type">class="kw">double</span> _max_lots ) { <span class="comment">class=class="str">"cmt">// 尝试计算结余曲线的斜率:</span> <span class="keyword">class="type">class="kw">double</span> current_slope = CalcSlope( ); <span class="comment">class=class="str">"cmt">// 如果指定交易额度不能积累, 以最小的交易量:</span> <span class="keyword">if</span>( GetRealTrades( ) < GetSlopePoints( )) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); <span class="keyword">class="kw">return</span>( rejected_lots ); }
◍ 斜率控仓的两种调节逻辑
基于结余曲线线性回归斜率做仓位调控时,无迟滞模式(STEP_WITHOUT_HYSTERESIS)的判断很直接:当前斜率低于中枢值就拒单并返回最小手数,否则返回最大手数。这种非黑即白的处理在斜率频繁穿越阈值时会造成手数来回跳变。
带迟滞的模式(STEP_WITH_HYSTERESIS)则看当前状态:若已处于拒单态且斜率落在区间内,维持最小手数;若已正常且斜率仍在区间内,维持最大手数。这样能避免边缘抖动,但代价是状态切换有粘性。
线性调节(LINEAR)走的是连续映射:用 a = (_max_lots - _min_lots) / (max_slope - min_slope) 算出斜率到手数的比例系数,再按 lots = a * _slope + b 插值。非线性分支目前只返回最小值,属于占位未实现。
开 MT5 把下面代码挂到 EA 里,改 min_slope / max_slope 两个边界,能直接观察不同账户结余斜率下手数输出的跳变点。外汇与贵金属杠杆高,斜率控仓仅降低暴露,不消除爆仓风险。
class=class="str">"cmt">// 如果调节功能无迟滞: if( control_type == STEP_WITHOUT_HYSTERESIS ) { if( current_slope < centr_slope ) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); class="kw">return( rejected_lots ); } else { current_lots_state = LOTS_NORMAL; normal_lots = trade_symbol.NormalizeLots( _max_lots ); class="kw">return( normal_lots ); } } class=class="str">"cmt">// 如果结余曲线的线形回归斜率小于许可: if( current_slope < min_slope ) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); class="kw">return( rejected_lots ); } class=class="str">"cmt">// 如果结余曲线的线形回归斜率大于指定: if( current_slope > max_slope ) { current_lots_state = LOTS_NORMAL; normal_lots = trade_symbol.NormalizeLots( _max_lots ); class="kw">return( normal_lots ); } class=class="str">"cmt">// 如果结余曲线的线形回归斜率在边界之内 (中间状态): current_lots_state = LOTS_INTERMEDIATE; class=class="str">"cmt">// 计算中间交易量的值: intermed_lots = CalcIntermediateLots( _min_lots, _max_lots, current_slope ); intermed_lots = trade_symbol.NormalizeLots( intermed_lots ); class="kw">return( intermed_lots ); } class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// 计算中间交易量: class=class="str">"cmt">//--------------------------------------------------------------------- class="type">class="kw">double TBalanceSlopeControl::CalcIntermediateLots( class="type">class="kw">double _min_lots, class="type">class="kw">double _max_lots, class="type">class="kw">double _slope ) { class="type">class="kw">double lots; class=class="str">"cmt">// 如果调节功能迟滞: if( control_type == STEP_WITH_HYSTERESISH ) { if( current_lots_state == LOTS_REJECTED && _slope > min_slope && _slope < max_slope ) { lots = _min_lots; } else if( current_lots_state == LOTS_NORMAL && _slope > min_slope && _slope < max_slope ) { lots = _max_lots; } } class=class="str">"cmt">// 如果调节功能是线性: else if( control_type == LINEAR ) { class="type">class="kw">double a = ( _max_lots - _min_lots ) / ( max_slope - min_slope ); class="type">class="kw">double b = normal_lots - a * .max_slope; lots = a * _slope + b; } class=class="str">"cmt">// 如果调节功能是非线性 (尚未实现): else if( control_type == NON_LINEAR ) { lots = _min_lots; } class=class="str">"cmt">// 如果调节功能未知: else { lots = _min_lots; } class="kw">return( lots ); }
把斜率控制塞进自己的 EA
把平衡曲线斜率控制接进 EA,核心就是六步:引库、开外部参数、声明对象、OnInit 里初始化、OnTick 里刷行情、开仓前算手数。代码里不再写死 NormalLots,而是交给 CalcTradeLots 按当前斜率状态返回手数,这样 EA 就能在斜率转负时自动砍仓。 随附的测试 EA 用 CCI 水平交叉做信号,仅用于回测验证,碰真实账户前请自重——外汇和贵金属杠杆交易随时可能吞掉本金。我们在 EURUSD H4 跑了 2008.07.01 到 2010.09.01 两段对照:UseAutoBalanceControl 设 No 时总净利润 18378,设 Yes 后降到 17261.73,但获利系数从 1.47 升到 1.81,回收系数从 2.66 拉到 3.74。 减仓换来的最直接好处是回撤被压扁:平衡最大减值从 5569.50(5.04%)缩到 3762.15(3.35%),净值最大减值从 6.19% 降到 4.08%。利润略薄是状态切换延迟的代价,但减仓保护和系数改善让 EA 在样本内更扛揍。 下面这段是接库和参数声明的骨架,逐行看:#include 把平衡斜率控制库拉进来;enum SetLogic 只定义 No/Yes 开关;外部参数里 UseAutoBalanceControl 默认关,BalanceControlType 选无迟滞步进模式。TradesNumberToCalcLR=3 表示用最近 3 笔交易算线性回归斜率,LRKoeffForRejectLots=-0.030 是触发砍仓的向下阈值,LRKoeffForRestoreLots=0.050 是恢复正常的向上阈值。RejectedLots=0.10 是砍后手数,NormalLots=1.0 是常态手数,最后声明 TBalanceSlopeControl 对象待初始化。
class="macro">#include <BalanceSlopeControl.mqh> class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// 控制结余曲线倾斜率的系统的参数; class=class="str">"cmt">//--------------------------------------------------------------------- enum SetLogic { No = class="num">0, Yes = class="num">1, }; class=class="str">"cmt">//--------------------------------------------------------------------- input SetLogic UseAutoBalanceControl = No; class=class="str">"cmt">//--------------------------------------------------------------------- input ControlType BalanceControlType = STEP_WITHOUT_HYSTERESIS; class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// 最后交易额度用于计算结余曲线的 LR: input class="type">int TradesNumberToCalcLR = class="num">3; class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// LR 斜率,降低交易量至最小: input class="type">class="kw">double LRKoeffForRejectLots = -class="num">0.030; class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// LR 斜率,恢复正常交易模式: input class="type">class="kw">double LRKoeffForRestoreLots = class="num">0.050; class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// LR 斜率,工作在中间模式: input class="type">class="kw">double LRKoeffForIntermedLots = -class="num">0.020; class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// 降低初始交易量至指定值,当 LR 倾斜向下 input class="type">class="kw">double RejectedLots = class="num">0.10; class=class="str">"cmt">//--------------------------------------------------------------------- class=class="str">"cmt">// 正常工作交易量,在资金管理为固定交易量模式: input class="type">class="kw">double NormalLots = class="num">1.0; TBalanceSlopeControl BalanceControl; class=class="str">"cmt">// 调整我们控制结余曲线斜率的系统: BalanceControl.SetTradeSymbol( Symbol( )); BalanceControl.SetControlType( BalanceControlType ); BalanceControl.SetControlParams( LRKoeffForRejectLots, LRKoeffForRestoreLots, LRKoeffForIntermedLots ); BalanceControl.SetSlopePoints( TradesNumberToCalcLR ); BalanceControl.SetFiltrParams( class="num">0, -class="num">1, class="num">0 ); BalanceControl.SetMonitoringBeginDate( class="num">0 ); class=class="str">"cmt">// 刷新市场情报: BalanceControl.RefreshSymbolInfo( ); if( UseAutoBalanceControl == Yes ) { current_lots = BalanceControl.CalcTradeLots( RejectedLots, NormalLots ); } else { current_lots = NormalLots; }
「虚拟盘隔离与算法升级的边界」
EA 进入不利运行周期时,可切到虚拟交易跑模拟单,实盘有效交易量不受影响,回撤减值也可能压下来。这套做法相当于给策略加了一层缓冲,不碰真实仓位就能观察拐点。 想进一步判断 EA 当前是否盈利状态,可上更复杂的算法,比如接一个神经网络做状态分类,但这块仍要大量验证,不是接上就灵。 和资金管理系统联跑,在部分场景下有可能在不抬风险的前提下改善收益特征。但必须认清:没有任何辅助模块能把持续亏损的 EA 硬扭成盈利 EA,外汇与贵金属杠杆高,错配算法只会放大亏损概率。 附带工程文件里 balanceslopecontrol.mqh 约 59 KB、bscs-testexpert.mq5 约 15.77 KB,直接丢进 MT5 跑一遍,比读十遍描述更实在。