不使用期权的期权交易(第一部分):基础理论与基于标的资产的模拟实现·综合运用
🧩

不使用期权的期权交易(第一部分):基础理论与基于标的资产的模拟实现·综合运用

(3/3)·从理论到落地,用现货头寸拼出市场里根本买不到的奇异期权组合

进阶 第 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 是总等级数,等级越多模拟越平滑,但标的交投手数也越大,受最小变动粒度卡着。下面是类声明的核心片段,逐行拆一下字段含义。

MQL5 / C++
<span class="comment">class=class="str">"cmt">// =====================================================================</span>
<span class="comment">class=class="str">"cmt">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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
{
&nbsp;&nbsp;<span class="keyword">class="type">int</span> IncreaseLevel_Array[];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// option emulation level when increasing it</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span> DecreaseLevel_Array[];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// option emulation level when decreasing it</span>
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span>
<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;ZeroDelta;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// delta value corresponding to the zero level of option emulation</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LevelsNumber;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// number of option emulation levels</span>
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zero_delta;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// integer delta value corresponding to the zero level of option emulation</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curr_option_level_sign;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// current sign of the option emulation level</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curr_option_level;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// current option emulation level</span>
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curr_inc_level;
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curr_dec_level;
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curr_contracts_index;
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span>
&nbsp;&nbsp;<span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;is_contracts_updated_Flag;
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span>
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//&nbsp;&nbsp;Constructor:</span>
&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// ---------------------------------------------------------------------</span>
&nbsp;&nbsp;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)
&nbsp;&nbsp;:
&nbsp;&nbsp;ZeroDelta(_ZeroDelta),
&nbsp;&nbsp;LevelsNumber(_levels_number),
&nbsp;&nbsp;curr_option_level_sign(<span class="number">class="num">0</span>),
&nbsp;&nbsp;curr_option_level(<span class="number">class="num">0</span>),
&nbsp;&nbsp;curr_inc_level(<span class="number">class="num">0</span>),
&nbsp;&nbsp;curr_dec_level(<span class="number">class="num">0</span>),
&nbsp;&nbsp;is_contracts_updated_Flag(<span class="macro">false</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;<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>);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Allocate memory for arrays storing emulation levels:</span>
&nbsp;&nbsp;&nbsp;&nbsp;<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>);
&nbsp;&nbsp;&nbsp;&nbsp;<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>);
&nbsp;&nbsp;&nbsp;&nbsp;<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,可立刻打印验证档位映射是否符合预期。外汇与贵金属杠杆高,这类离散化仅用于概率分层,不预示价格走向。

MQL5 / C++
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 的边界值符合你的风控阈值。

MQL5 / C++
  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 交易函数怎么维护这套持仓再平衡,那时候就能直接抄函数了。

把组合诊断交给小布盯盘
这些合成头寸的希腊值和盈亏剖面,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时模拟,你只管判断结构要不要调。

常见问题

美式可在到期前任意日行权,模拟时需预留提前平仓或反向头寸的流动性;欧式只需盯到期日当天损益,中间不必频繁调整delta。
现金交割直接以差额结算,模拟用等价合约平仓即可;实物交割需考虑标的实际过户成本与交割规则,外汇贵金属高风险下更要算清点差。
理论上任意行权价与到期日的线性组合都能拼,但非线性的奇异条款可能需多层头寸,执行误差会随层数放大。
目前小布盯盘提供已有头寸的剖面诊断与预警,组合构建仍由交易者设定,它不代你下决策,只把重复劳动交给小布。
百慕大仅在特定日可行权,模拟时在那些日期前调整保护性头寸即可,完整讨论见《不使用期权的期权交易·基础篇》。