不使用期权的期权交易(第一部分):基础理论与基于标的资产的模拟实现·综合运用
(3/3)·从理论到落地,用现货头寸拼出市场里根本买不到的奇异期权组合
用滞后回差压住手数抖动
把期权δ映射成标的具体持仓手数,第一步是把 0.0~1.0 的δ拉到 0~10 的整数档。直接 int(δ*10) 最省事,但边界附近会出事:δ在 0.090 时手数算 0,涨到 0.101 变 1,下一根 K 线回落到 0.090 又归 0,几秒内反复开平,这种信号抖动(chattering)在外汇和贵金属这种点差敏感品种上会吃掉不少成本,属于典型高风险陷阱。 正经做法是给再平衡加滞后回差:加仓和减仓用两套判定阈值。原文用两个数组 IncreaseLevel_Array 和 DecreaseLevel_Array 分别存上调、下调时的模拟等级边界,UpdateOptionLevel 方法吃当前δ和上一次等级,靠方向区分避免原地跳变。 OptionContractsVolume 类构造时收两个参数:_ZeroDelta 是等级 0 对应的δ阈值,范围 0.001~0.099;_LevelsNumber 是总等级数,等级越多模拟越平滑,但标的交投手数也越大,受最小变动粒度卡着。下面是类声明的核心片段,逐行拆一下字段含义。
<span class="comment">class=class="str">"cmt">// =====================================================================</span> <span class="comment">class=class="str">"cmt">// Class for calculating the contract volume of the underlying asset for an option:</span> <span class="comment">class=class="str">"cmt">// =====================================================================</span> <span class="keyword">class</span> OptionContractsVolume { <span class="keyword">class="type">int</span> IncreaseLevel_Array[]; <span class="comment">class=class="str">"cmt">// option emulation level when increasing it</span> <span class="keyword">class="type">int</span> DecreaseLevel_Array[]; <span class="comment">class=class="str">"cmt">// option emulation level when decreasing it</span> <span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span> <span class="keyword">class="kw">protected</span>: <span class="keyword">class="type">class="kw">double</span> ZeroDelta; <span class="comment">class=class="str">"cmt">// delta value corresponding to the zero level of option emulation</span> <span class="keyword">class="type">int</span> LevelsNumber; <span class="comment">class=class="str">"cmt">// number of option emulation levels</span> <span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span> <span class="keyword">class="type">int</span> zero_delta; <span class="comment">class=class="str">"cmt">// integer delta value corresponding to the zero level of option emulation</span> <span class="keyword">class="type">int</span> curr_option_level_sign; <span class="comment">class=class="str">"cmt">// current sign of the option emulation level</span> <span class="keyword">class="type">int</span> curr_option_level; <span class="comment">class=class="str">"cmt">// current option emulation level</span> <span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span> <span class="keyword">class="type">int</span> curr_inc_level; <span class="keyword">class="type">int</span> curr_dec_level; <span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span> <span class="keyword">class="type">int</span> curr_contracts_index; <span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span> <span class="keyword">class="type">bool</span> is_contracts_updated_Flag; <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">// Constructor:</span> <span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span> OptionContractsVolume(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> _ZeroDelta, <span class="keyword">const</span> <span class="keyword">class="type">int</span> _levels_number) : ZeroDelta(_ZeroDelta), LevelsNumber(_levels_number), curr_option_level_sign(<span class="number">class="num">0</span>), curr_option_level(<span class="number">class="num">0</span>), curr_inc_level(<span class="number">class="num">0</span>), curr_dec_level(<span class="number">class="num">0</span>), is_contracts_updated_Flag(<span class="macro">false</span>) { <span class="keyword">this</span>.zero_delta = (<span class="keyword">class="type">int</span>)(<span class="functions">NormalizeDouble</span>(<span class="keyword">this</span>.ZeroDelta, <span class="number">class="num">3</span>) * <span class="number">class="num">1000.0</span>); <span class="comment">class=class="str">"cmt">// Allocate memory for arrays storing emulation levels:</span> <span class="functions">ArrayResize</span>(<span class="keyword">this</span>.IncreaseLevel_Array, (<span class="keyword">this</span>.LevelsNumber + <span class="number">class="num">1</span>) * <span class="number">class="num">100</span> + <span class="number">class="num">1</span>); <span class="functions">ArrayResize</span>(<span class="keyword">this</span>.DecreaseLevel_Array, (<span class="keyword">this</span>.LevelsNumber + <span class="number">class="num">1</span>) * <span class="number">class="num">100</span> + <span class="number">class="num">1</span>); <span class="comment">class=class="str">"cmt">// Fill in the array upwards:</span>
◍ 用数组把期权Delta映射成档位
这段逻辑在初始化时把 Delta 区间离散化进两个一维数组:IncreaseLevel_Array 管上行、DecreaseLevel_Array 管下行。每个档位预留 LevelsNumber*100 个槽位,索引直接由 Delta 千分位整数定位,省去运行时查表判断。 初始化循环里,上行数组按 i 从 0 到 LevelsNumber 逐段填值,每段长度 LevelsNumber*100;下行数组则先填零区 zero_delta,再从第 2 段起反向填 i。末尾都补一行把越界索引 ((LevelsNumber+1)*100) 固定成 LevelsNumber,对应深度实值状态。 UpdateOptionLevel 接收 _delta 与 _curr_level:先把 delta 乘 1000 并保留 3 位小数转成 int,若大于 zero_delta/2 记方向 +1,小于负半记 -1,否则为 0。curr_contracts_index 取绝对值,超过 (LevelsNumber+1)*100 就钳到上限,防止越界。 随后用该索引直接取数组值赋给 curr_inc_level 与 curr_dec_level。你在 MT5 里把 LevelsNumber 设成 5,数组总长就是 600,Delta 0.012 会落进索引 12,可立刻打印验证档位映射是否符合预期。外汇与贵金属杠杆高,这类离散化仅用于概率分层,不预示价格走向。
for(class="type">int i = class="num">0; i < this.LevelsNumber + class="num">1; i++) { ArrayFill(this.IncreaseLevel_Array, i * this.LevelsNumber * class="num">100, this.LevelsNumber * class="num">100, i); } ArrayFill(this.IncreaseLevel_Array, (this.LevelsNumber + class="num">1) * class="num">100, class="num">1, this.LevelsNumber); class=class="str">"cmt">// Fill in the array downwards: ArrayFill(this.DecreaseLevel_Array, class="num">0, zero_delta, class="num">0); ArrayFill(this.DecreaseLevel_Array, zero_delta, this.LevelsNumber * class="num">100 - zero_delta + class="num">1, class="num">1); for(class="type">int i = class="num">2; i < this.LevelsNumber + class="num">1; i++) { ArrayFill(this.DecreaseLevel_Array, i * this.LevelsNumber * class="num">100, this.LevelsNumber * class="num">100, i); } ArrayFill(this.DecreaseLevel_Array, (this.LevelsNumber + class="num">1) * class="num">100, class="num">1, this.LevelsNumber); } class=class="str">"cmt">// --------------------------------------------------------------------- class=class="str">"cmt">// Calculate the option emulation level for a given delta: class=class="str">"cmt">// --------------------------------------------------------------------- class=class="str">"cmt">// - if the level has changed, class="kw">return &class="macro">#x27;true&class="macro">#x27;. class=class="str">"cmt">// --------------------------------------------------------------------- class="type">bool UpdateOptionLevel(const class="type">class="kw">double _delta, const class="type">int _curr_level) { this.is_contracts_updated_Flag = false; class=class="str">"cmt">// Define trade direction: class="type">int delta_int = (class="type">int)(NormalizeDouble(_delta, class="num">3) * class="num">1000.0); this.curr_option_level_sign = class="num">0; if(delta_int > zero_delta / class="num">2) { this.curr_option_level_sign = class="num">1; } else if(delta_int < -zero_delta / class="num">2) { this.curr_option_level_sign = -class="num">1; } class=class="str">"cmt">// Current index for arrays, based on &class="macro">#x27;Delta&class="macro">#x27; with class="num">3 decimal places: this.curr_contracts_index = (class="type">int)MathAbs(delta_int); if(this.curr_contracts_index > ((this.LevelsNumber + class="num">1) * class="num">100)) { class=class="str">"cmt">// The index should not exceed the array size(here the option is deep in the money): this.curr_contracts_index = (this.LevelsNumber + class="num">1) * class="num">100; } class=class="str">"cmt">// Current option emulation level(class="num">0...N) in the direction of INCREASE: this.curr_inc_level = this.IncreaseLevel_Array[this.curr_contracts_index]; class=class="str">"cmt">// Current option emulation level(class="num">0...N) in the direction of DECREASE: this.curr_dec_level = this.DecreaseLevel_Array[this.curr_contracts_index];
「期权模拟档位跳变时的合约刷新逻辑」
在 MT5 自建的期权模拟器里,档位(0…N)不是静态的,它会随行情波动上下切换。上面这段类方法做的就是「档位变了就重算合约」的守门员。 当内部记录的递增档位 curr_inc_level 大于传入的当前档位 _curr_level,说明模拟期权被向上抬了一档。此时把 curr_option_level 同步成 curr_inc_level,打上 is_contracts_updated_Flag 为真,直接 return(true) 告知调用方「合约已更新」。 反过来,若递减档位 curr_dec_level 小于 _curr_level,意味着档位下沉,同样改写 curr_option_level 与更新标志并返回真。两种跳变都没命中就返回 false,调用方可以据此跳过不必要的重算。 实盘接这段逻辑时,外汇与贵金属杠杆高、滑点随机,档位切换触发的合约重估可能带来瞬时保证金占用跳变,建议先在策略测试器用历史 Tick 跑一遍,确认 curr_inc_level / curr_dec_level 的边界值符合你的风控阈值。
class=class="str">"cmt">// If the option emulation level(class="num">0...N) has INCREASED compared to the current one: if(this.curr_inc_level > _curr_level) { this.curr_option_level = this.curr_inc_level; this.is_contracts_updated_Flag = true; class="kw">return(true); } class=class="str">"cmt">// If the option emulation level(class="num">0...N) has DECREASED compared to the current one: if(this.curr_dec_level < _curr_level) { this.curr_option_level = this.curr_dec_level; this.is_contracts_updated_Flag = true; class="kw">return(true); } class="kw">return(false); }
画得少,看得清
用标的资产模拟期权,本质是把非线性权利转化为动态头寸调整:价格顺向走就加仓、逆向走就减仓,从而逼近买入看涨或看跌期权的收益轮廓。它适合没有场内期权品种的市场,比如部分贵金属与外汇交叉盘,但使用者必须吃透背后的数学模型,否则再平衡步长一错就全变形。 评论区里 Aleksey Vyazmikin 提到,若只设 1–10 的价格步长,模拟精度根本不够,必须放大虚拟期权规模;作者回了一句关键约束——步数由交易品种的最小手数/最小变动量钉死,不能拍脑袋改。这意味着同一套模拟逻辑,在 XAUUSD 和 EURUSD 上的可行参数会差出量级。 这类方法在流动性缺失的对冲场景里概率上更划算,但外汇与贵金属自带高杠杆高风险,实盘前务必在 MT5 策略测试器里把虚拟期权和实际期权在实值/虚值两种到期情形下的成本拉出来比对。下一篇会落到 MQL5 交易函数怎么维护这套持仓再平衡,那时候就能直接抄函数了。