单一资产交易顺序中的风险评估. 续篇(基础篇)
从单资产顺序交易切入风险评估
这篇接着前一篇没讲完的风险评估思路,把重心放在单一资产按固定顺序开平仓时的暴露测算上,并用两个具体例子把推导收口。外汇与贵金属杠杆高、跳空频繁,这类顺序化风险模型只能给出概率层面的参考,不能当作确定性结论。 原文先列了收益分布、固定止损/获利、固定跟踪止损几个前置假定,说明后续例子都建立在同样的统计底座上。对 MT5 用户来说,这意味着你回测时若改了止损逻辑,下面那套推导就得重算,不能直接套用。 除收口例子外,作者还顺带点出概率论在统计跳空、机器学习特征构造、系统参数优化上的其他落点。想验证的话,开 MT5 用策略测试器跑一遍固定跟踪止损的 equity 曲线,对照本文的分布假设即可。
「把价格和系统先降维成随机游走」
上一篇文章的方法并不能直接套到任意交易系统上。前提是它的交易序列必须是独立且同分布的(i.i.d.),但实战里这个条件经常被打破——要么交易之间互相依赖,要么每笔的分布都不一样。 Ralph Vince 在《资金管理的数学》里给过一种拆法:把系统切成满足 i.i.d. 的子系统。比如把奇数、偶数笔交易当成两个不同系统;金字塔加仓则把每一步加仓视作独立系统。但切完还得用类似马科维茨组合理论把“片段”拼成微型投资组合,复杂度直接起飞。
| 更麻烦的是固定止盈止损比的系统。设 k= | p_takeprofit-p_enter | / | p_stoploss-p_enter | ,若每笔 k 都不同,单笔收益只在 -1 和 k 两个值间跳,但每笔分布都不同,切出来的“片段”数等于交易数,毫无意义。实务上常取一个平均 k 做简化版系统来分析。 |
|---|
真要做风险分析,可以三条路并行:拆系统、研究简化版、不验 i.i.d. 直接用其他手段微调风险值。但这套不通用。更系统的做法是先对价格和系统做假定。 假定价格序列近似随机波动:真实价格做某种计算后能当随机游走用,结果等价。系统吃随机波动报价时,“平均来看”随机价要贴近真价——高周期动荡类似、分时波动分布类似。两种情况下交易间的依赖结构类似,但收益分布可能差很多;比如所有收益同额平移,分布右移但相关系数不变。 系统自身假定为:交易基本随机分布,仅当第二笔紧接第一笔平仓后开启时才出现强内部依赖。随机游走分离散/连续两类,计算机建模只用离散;可对称(无趋势)或有偏移(有趋势),最常讨论对称情形。 对称随机游走下两个推论很关键:一是交易结果无法用任何指标预测,建模只需研究出场部分,入场在哪、怎么开都无所谓;二是长期期望利润为 0,单笔可赚任何数,但交易多了结果聚集服从某概率分布,期望恒为零。 外汇与贵金属属高风险市场,这些假定只是建模起点,实盘前必须用真价回测验证偏离对称游走的显著性。系统限制也随之而来:入场出场规则要简单、统一、透明,不能让交易间额外耦合(如引用余额曲线)、不能随机改止盈止损比、交易不能过频。理论上可检查,也可直接用蒙特卡洛在计算机上跑出来看近似程度。
◍ 混合分布下的退出机制与风险反算
收益分布并不只有离散和连续两种教科书形态。实际平仓逻辑里,固定止损/止盈给出的是离散台阶,而按持仓时间 t 强平则落在连续区间,两者加权叠加就构成混合分布:P(x)=γc·Pc(x)+γd·Pd(x),其中 γc、γd 非负且和为 1。 若把时间退出阈值设得足够大,γc 可近似忽略,分布退化成纯离散 Pd(x);而启用固定跟踪止损时 γd=0,属纯连续情形。更现实的是 γd 与 γc 均非零——比如前 N 根 K 线锁死初始止损、满足条件后才转为移动止损,这段静默期就是离散成分。 下面给出的脚本面向单一种类分布算最优风险值 ropt。但实战常反过来:已知风险额度,要反推可靠性 δ 或最大平均收益 G0。这时不必改架构,换输入重跑或微调脚本即可,外汇/贵金属杠杆放大下这类反算误差会直接放大爆仓概率,建议先在 MT5 策略测试器跑通再上实盘。
用离散收益模型给系统判生死
把每笔交易收益压成两个离散值:亏固定 1 单位,赚 k 单位,概率分别是 1-θ 和 θ。期望值 M=(1+k)θ-1,对称随机游走时 M=0 对应 θ0=1/(1+k)。样本里若估算期望 Mapr≤0,这系统直接否掉——任何风控都救不回负期望的盘子,外汇贵金属这种高杠杆品种尤其别幻想补窟窿。 真要留系统,先证它不在随机游走里混。用 S=nk(获利笔数)当检验量,原假设 M=0 下服从 B(n,θ0)。累积概率 P(S) 得超过 P(nθ0)+δ/2,δ 取 0.05 时就是右尾显著,过不了这关也砍。 过了显著性,还要看最小平均收益 G0 够不够。把 S 按实际 θapr 重新套二项规则,G0 必须压过 δ 分位数;不够就逐步加样本量 n 直到满足,算出 nmin。默认脚本里 G0=0.15、kt=3、pk=0.5、na=50,跑出来 ropt=0.011,即单笔冒 1.1% 本金的系统最优风险参考值,MT5 里改 input 就能复算。 蒙特卡洛那段才是落地重点:模拟万组各 n 笔的收益序列,每组按第一部分算法提取 ropt,i,堆出经验分布再取 δ 分位数当实盘风险上限。代码里 NB=10000 控制模拟组数,NR=1000 管风险轴分区,调大 NB 可能让尾部估计更稳但更慢。
class="macro">#include <Math\Stat\Binomial.mqh> class="macro">#class="kw">property script_show_inputs class="kw">input class="type">uint na=class="num">50; class=class="str">"cmt">// 序列中的交易数量 class="kw">input class="type">class="kw">double kt=class="num">3; class=class="str">"cmt">// 获利对止损的比例 class="kw">input class="type">class="kw">double pk=class="num">0.5; class=class="str">"cmt">// 获利的概率 class="macro">#define NX class="num">300 class=class="str">"cmt">// 序列中的最大交易数量 (用于搜索 nmin) class="macro">#define NB class="num">10000 class=class="str">"cmt">//为蒙特卡洛方法生成的样本数量 class="macro">#define NR class="num">1000 class=class="str">"cmt">// 风险的间隔分区数 class="type">class="kw">double G0=class="num">0.15; class=class="str">"cmt">// 最低平均收益 class="type">class="kw">double D0=class="num">0.9; class=class="str">"cmt">// 最低最小增益 class="type">class="kw">double dlt=class="num">0.05; class=class="str">"cmt">// 显著性水平 class="type">void OnStart() { if (D0<=class="num">0||D0>=class="num">1) {Print("D0 必须是正的并且小于1"); class="kw">return;} class="type">class="kw">double m=(class="num">1+kt)*pk-class="num">1; class=class="str">"cmt">// 期望值 if(m<=class="num">0) {Print("期望值必须为正"); class="kw">return;} class="type">class="kw">double s=na*pk; class=class="str">"cmt">// 统计 (获利交易平均数量) class="type">class="kw">double s0=na/(class="num">1+kt); class=class="str">"cmt">// 在0期望值时的获利交易平均数量 class="type">int ner; class="type">class="kw">double p=MathCumulativeDistributionBinomial(s,na,class="num">1/(class="num">1+kt),ner); if(!MathIsValidNumber(p)) {Print("MathIsValidNumber(p) 错误 ",ner); class="kw">return;} class="type">class="kw">double p0=MathCumulativeDistributionBinomial(s0,na,class="num">1/(class="num">1+kt),ner); if(!MathIsValidNumber(p0)) {Print("MathIsValidNumber(p0) 错误 ",ner); class="kw">return;} if(p<=p0+dlt/class="num">2) {Print("期望值近似于0"); class="kw">return;} class="type">class="kw">double q=MathQuantileBinomial(dlt,na,pk,ner),q0=na*(G0+class="num">1)/(kt+class="num">1); if(!MathIsValidNumber(q)) {Print("MathIsValidNumber(q) 错误 ",ner); class="kw">return;} if(q0>q) { Print("G0 大于收益算数平均值的分值 dlt "); class="type">uint nmin=na+class="num">1; for(;nmin<=NX;++nmin) { q=MathQuantileBinomial(dlt,nmin,pk,ner); q0=nmin*(G0+class="num">1)/(kt+class="num">1); if(!MathIsValidNumber(q)) {Print("MathIsValidNumber(q) 错误 ",ner); class="kw">return;} if(q0<=q) class="kw">break;