不使用期权的期权交易(第一部分):基础理论与基于标的资产的模拟实现
引言
期权这一金融工具正受到越来越多市场参与者的关注。仅举一例:在过去几年MOEX举办的个人投资者竞赛中,期权交易者始终能够在期货组别中斩获奖项。
期权的数学理论相对复杂,人工理解与计算较为困难,但市面上已有大量所谓期权计算器程序,可用于计算各类期权组合。实际交易中,通常会交易一组不同参数的期权(即期权组合)。相关内容将在后续文章中详细介绍。
本文描述通过买卖标的资产来模拟期权的方法。该方法允许我们构建(模拟)具有任意所需参数的合成期权,包括现实市场中不存在的期权结构。例如,我们可以构建期权的期权、价差期权,以及其他类型的真实或合成标的资产对应的奇异期权。
期权理论基础
- 1. 定义
期权是一种金融衍生品,赋予交易参与者在以下操作中的权利(而非义务):
- 买入标的资产 —— 看涨(Call)期权;
- 卖出标的资产 —— 看跌(Put)期权。
对于期权卖方(即买方的对手方)而言,该合约不再是权利,而是义务(如果是看涨期权,则有义务向买方卖出标的资产;如果是看跌期权,则有义务从买方买入标的资产)。作为对价,卖方向买方收取权利金。
标的资产的买入或卖出将以约定价格,在约定到期日当天或之前完成。当期权行权或到期后:对于实物交割期权,交易者收到相应数量的买入/卖出标的资产(股票、期货、货币等);对于现金交割期权,交易者获得行权结果对应的现金差额。
- 2. 术语和定义
标的资产 —— 期权所挂钩的股票、指数、货币、商品或其他金融工具;
行权价 —— 预先约定的、可用于买入或卖出标的资产的价格,也称期权执行价;
权利金 —— 期权价格,即买方向卖方支付的费用,用以获得在约定时间以约定价格买卖标的资产的权利;
到期日 —— 期权到期的日期,在此日期之前,买方可行使其买卖标的资产的权利。
- 3. 期权类型
- 3.1. 按行权方式划分
美式期权 —— 可在到期日前任意一天行权(通常价格高于欧式期权);
欧式期权 —— 仅可在到期日当天行权;
百慕大期权 —— 仅可在特定日期行权。
- 3.2. 按交割方式划分
实物交割 —— 交割标的资产本身;
现金交割 —— 以现金形式结算盈亏。
- 3.3. 按交易场所划分
场内期权 —— 标准化合约,在交易所挂牌交易(如CBOE、MOEX);
场外期权(OTC)—— 交易双方自定义条款的个性化合约。
- 4. 期权的工具本质
期权是优秀的对冲工具,可用于保护投资组合。期权价格(权利金)决定了您愿意为部分或完全消除风险所付出的成本。例如,您希望在六个月内对冲以卢布计价的资产,防范卢布对美元升值的风险。假设您将在六个月后需要一笔卢布用于大额支出。
一种对冲方式是直接买入标的资产,即用卢布买入美元。但这会产生新风险:如果在此期间卢布对美元持续升值,您在换回卢布时将面临巨额亏损。例如,汇率从1美元 = 100卢布跌至1美元 = 80 卢布,则您将亏损20%的初始卢布本金。
第二种对冲方式是买入期限为六个月、行权价约等于当前汇率(假设1美元 = 100卢布)的卢布兑美元(RUBUSD)看跌期权。支付权利金后,我们便可在六个月内按100卢布/美元这一约定价格行使该看跌期权,从而按该价格获得相应的卢布头寸。即便届时卢布大幅升值至(难以想象的)50卢布 = 1美元,您最大的损失也仅为期权权利金。
同理,如果我们要对冲卢布贬值风险,则需买入行权价接近当前RUBUSD汇率的看涨期权(Call)。
- 5. 布莱克 - 斯科尔斯期权定价模型
布莱克 - 斯科尔斯模型是1973年由费舍尔・布莱克、迈伦・斯科尔斯以及(间接参与)罗伯特・默顿提出的数学公式,用于计算欧式期权的公允价格。此处的公允价格,是指能同时满足期权买方与卖方的均衡价格。斯科尔斯与默顿凭借该研究成果获得了1997年诺贝尔经济学奖(费舍尔・布莱克此前逝世,未能获奖)。
看涨期权价格(权利金):
![]()
看跌期权价格(权利金):
![]()

![]()
其中:
- S —— 标的资产当前价格;
- X —— 期权行权价;
- r —— 无风险利率(连续复利);
- T —— 距到期日的剩余时间(以年为单位);
- N (d) —— 标准正态分布累积分布函数;
- σ —— 标的资产波动率。
该模型基于若干假设:
- 市场完全有效,不存在套利空间;
- 无风险利率恒定且已知;
- 标的资产波动率恒定;
- 模型假设不考虑股息支付(该模型后续已优化);
- 资产价格服从对数正态分布,价格不可能为负;
- 时间连续,交易不间断;
- 无交易佣金与税费。
由上述假设可推导出模型的主要缺陷:
- 市场并非完全有效,只有流动性极高的品种在一定程度上接近这一状态;
- 标的资产波动率频繁且持续变化,无法视为常数;
- 实际分布远非对数正态,真实市场存在“厚尾”特征(崩盘风险),而模型忽略了价格的突发性波动;
- 不适用于美式期权定价。
目前已有可纳入上述部分因素的替代期权定价模型:
- 赫斯顿模型(随机波动率);
- 贝叶斯模型(纳入宏观因子);
- 机器学习方法(神经网络预测期权价格)。
- 6. “希腊字母”(风险指标)
这些希腊字母是统计变量,有助于评估期权价格对各种参数变化的敏感度,包括行权价、波动率、标的资产当前价格、到期日等。从数学角度讲,“希腊字母”是布莱克-斯科尔斯方程的偏导数。在实际交易中,主要使用以下核心指标:
- δ衡量标的资产价格变动时期权价值的变化幅度。它是期权价格变化与资产价格变化的比率。如果δ为负,当资产价格上涨时期权价格就会下跌。看涨期权δ为正,看跌期权δ为负。这是期权模拟过程中最为重要的指标。
- γ显示了在标的资产价格发生变化时δ是如何变化的。本质上讲,它是期权价格对标的资产价格的二阶导数。距到期日时间较长的期权γ最小,临近到期时γ会逐渐增大。
- θ衡量期权价格随到期时间临近而发生的变化。它是期权价格变化与到期时间变化的比率。θ始终为负值,因为时间流逝会持续损耗期权价值。
- Vega显示期权价值如何随隐含波动率变化而变化。它是期权价格变化与隐含波动率变化的比率。行权价接近当前资产价格的期权具有最大的Vega值。这些期权对隐含波动率的变化最为敏感。Vega随期权到期日临近而减小。
- 隐含波动率(IV)虽不属于“希腊字母”,但却是布莱克 - 斯科尔斯模型中的重要指标。它根据特定期权合约的实际成交数据反推得出。此外,波动率的计算方式多样:可通过布莱克-斯科尔斯方程拟合、仅针对平值行权价计算、按不同行权价加权、考虑或不考虑挂单数据等。在期权模拟中,我们假设标的资产不存在真实期权及相关订单,或无法获取此类数据。
因此,我们将使用历史波动率(HV)替代隐含波动率,计算周期分为日、周、月,具体周期根据期权剩余到期时间确定。从数学角度讲,历史波动率是衡量一段时间内数据相对于均值离散程度的统计指标,计算方式为标准差乘以时间周期数T的平方根。本文采用日、周、月作为计算周期。
期权模拟
- 1. 模拟期权的必要性
模拟期权的需求源于以下几方面因素:
- 目标标的资产本身不存在场内期权。例如,Bybit加密货币交易所仅提供BTC、ETH、SOL三个现货品种的期权交易。而MOEX此前也长期未上市股票期权。
- 期权到期类型不符合交易需求。例如,MOEX的股票期权仅提供欧式期权,且仅能在到期日执行。这样极大限制了期权策略的范围,剔除了大部分投机类策略。
- 流动性不足,难以开仓或提前平仓。在MOEX只有流动性最高的期货/股票期权才能实现快速开仓或平仓。流动性不足会直接导致利润回吐或非系统性亏损,也使得剥头皮策略难以实施,因为这类策略对开仓或平仓速度要求极高。
- 2. 真实期权与模拟期权对比
| 对比项 | 真实期权 | 模拟期权 |
|---|---|---|
| 买入期权时是否需支付权利金 | 需支付 | 无需支付 |
| 卖出期权时是否需收取权利金 | 需收取 | 不收取 |
| 临近到期时是否存在时间价值衰减 | 存在 | 不存在 |
| 后续市场波动及消息面冲击是否有影响 | 基本无影响 | 影响模拟效果 |
| 到期时间选择是否具有灵活性 | 无灵活性,由交易所规定 | 用户自定义 |
| 是否支持极短到期周期(日、小时、分钟级) | 不支持 | 可设置任意时长 |
| 是否需要持续监控持仓 | 通常无需 | 必须持续监控 |
以上加粗部分为该类期权的优势。由表格可见,两类期权各有优劣。正如我们预期的那样,在现实世界中并不存在完美的方案。
- 3. 使用期权模拟的交易条件要求
由于期权模拟需要通过交易标的资产实现,因此应尽量降低佣金及其他交易损耗成本,例如滑点、大额点差以及持仓隔夜利息。这些成本都会降低模拟期权的效果,在短到期周期的情况下尤为明显。除此之外,对交易条件没有其他特殊要求。
要说明标的资产持仓等价于期权持仓,必须理解:期权在任意时刻都对应一个确定的δ值。如果标的资产的持仓数量与期权δ相匹配,那么这两个头寸在该时刻就是等价的,即盈亏表现完全一致。
让我们详细讲解如何构建模拟期权。
假设我们需要在某一标的资产上买入或卖出期权(或多个期权组合)。首先计算该头寸的δ(如果是场内期权,可在MOEX官网等平台查询),就像我们直接通过期权建仓一样。
让我们来研究以下示例。假设我们计划买入100张实值看涨期权,以杠杆参与上涨行情并实现风险有限。单张期权δ为0.5,那么100张期权组合的总δ为:0.5 × 100 = 50。要构建等价组合,我们只需要买入50单位标的资产。此时两个组合的δ相同,均为50。
然而,买入真实期权后,通常可以持有至到期无需管理。模拟期权则不同。在整个持仓周期内,必须持续保持与期权组合等价,因为市场价格在不断变化。因此需要进行调整(再平衡),即买入或卖出多余/不足数量的标的资产。再平衡应以何种频率、在什么时点执行?可以按固定时间步长(每小时、每日)执行,也可以按δ步长执行(例如δ变动 ±1、±2时调整)。此外还有其他方式。
让我们继续上面的例子。我们已买入50单位标的资产,等价于100张看涨期权。到了傍晚,市场小幅上涨,单张看涨期权的δ升至0.53,0.53 × 100 = 53。为维持等价,我们需要再买入3单位标的资产。如果次日价格回落至开仓价位下方,期权δ降至0.48,则我们需要卖出5单位标的资产。通过这种方式,持续维持组合等价性。
为什么模拟期权的盈亏结构与真实买入期权一致?因为当价格上涨时,δ上升,交易者会买入更多标的资产。期权越趋向“实值”,组合中持有的标的资产越多,最终在上涨中获利,与买入看涨期权效果一致。如果价格下跌,则逐步卖出标的资产,直至仓位归零。这样保证了下跌端的损失有限,与看涨期权特性相同。
关于再平衡的更多细节。再平衡是模拟期权的核心环节,最终收益很大程度上取决于这些调整交易。调整必须系统化、按预设算法执行,否则可能出现意外亏损。例如,行情从δ为0.5快速拉升至0.6,如果错过再平衡、未买入10单位标的,而到期前价格可能不再回到该区间。次日,δ可能已到0.65,届时只能在0.65位置买入15单位,而不是在0.6买10单位、0.65买5单位。显然,期权模拟必须借助交易EA自动执行。
理想情况是期货合约24小时交易,可连续调整仓位。但频繁调整也可能产生看似不必要的亏损。如果市场三天内δ的波动为:0.50 → 0.55 → 0.50,按再平衡规则,需先在0.55买入5单位,次日在0.50卖出5单位,这样会锁定亏损。因此,如果交易者因某种原因在第二天未调整,只要价格最终回到原点,并不会造成严重后果。市场也可能沿单一方向持续运行,因此必须定期执行再平衡。
“时间再平衡”的缺点是存在剧烈价格波动,即市场在短时间内上涨或下跌数个百分点的情形。通过δ步长调整能较好地应对这类情况,但在极端剧烈行情下,仍可能因滑点过大而无法在目标价位成交。这种情况下,交易EA能更快、更精准地调整仓位。
显而易见,当标的资产来回震荡时,每次再平衡都会产生小额亏损。在买入看涨期权的模拟案例中,交易者本质上是高买低卖标的资产。如果持仓期间标的资产基本无趋势,策略会因δ维护而产生亏损。但是当买入真实期权时,需要支付权利金。如果到期未能变为“实值”,权利金也会全部亏损,这是因为时间价值衰减会不断侵蚀期权价值。在模拟期权中,亏损则来自再平衡操作。
为维持δ而进行的标的资产买卖,都是小额亏损交易,其累计总亏损长期来看近似等于期权权利金。权利金高低取决于买入时的隐含波动率IV。IV越高,市场就越会如交易者所预期的那样"波动",期权也越贵。模拟期权也是同理:市场波动越频繁、波动率越高,模拟成本就越高。波动越大,再平衡亏损越多,与波动率上升推高期权价格的逻辑一致。
如果期权存续期内标的资产波动率较低,再平衡次数少、单次亏损小,那么模拟期权会比在交易所高价买入期权更划算。因为组合存续期内的实际波动率远低于买入时的隐含波动率IV。这一逻辑同样适用于卖出期权的类比。例如以52%波动率卖出期权,如果实际波动率为60%,则因维持δ中性会产生亏损;如果实际波动率仅45%,再平衡成本低于权利金,卖出操作就是盈利的。
如果实际波动率超过建仓时的隐含波动率IV,则模拟期权的表现不如直接买入期权,因为剧烈市场波动导致的频繁调整成本,会超过期权权利金。反之,如果模拟做空期权,实际波动率越高,模拟效果往往比直接卖出期权更有利。
人工构建期权还需要了解什么?期权价格主要由三个因素决定:到期剩余时间、标的价格和隐含波动率。在其他条件相同的情况下,每个因素都以各自的方式影响期权价值。可以认为,标准期权的价值是这三个变量的函数。而模拟期权仅由标的资产构成,其价格不直接依赖隐含波动率与时间。“人工”期权的价格仅仅是标的资产价值的函数。这究竟是好还是坏?显然,它既是优势也是劣势。
在实现模拟算法之前,我们先看实操层面的内容。为此我们绘制期权δ与盈亏(P&L)随标的价格变化的曲线图。这些图表取自MOEX官网,标的为SBRF期货期权,行权价31000,到期日2025年6月18日,图表数据日期为2025年5月22日。图中垂直虚线对应当时的现价,约为30800。
图例1. 行权价31000的看涨期权买入持仓盈亏与标的资产价格的关系。盈亏以卢布计,按每张期权计算。图中垂直虚线为图表绘制时点。在该时点,盈亏为负值,即-23.92卢布。
红线代表当前时点(2025年5月22日),蓝线代表到期时点(2025年6月18日),即布莱克 - 斯科尔斯公式中T=1的情况。
图例2. 行权价31000的看涨期权买入持仓的δ值与标的资产价格的关系(单张期权)。图中垂直虚线为图表绘制时点。在该时点,δ为0.48。
红线代表当前时点(2025年5月22日),蓝线代表到期时点(2025年6月18日),即布莱克 - 斯科尔斯公式中T=1的情况。
以上图表均直接由布莱克 - 斯科尔斯公式推导得出。由图例1可见 ,买入看涨期权时,我们需要向卖方支付约789卢布/张的权利金。也就是说,开仓后账户立即处于亏损状态。而模拟期权则不同(标的资产的交易成本在近似计算中可忽略)。但好消息是,即便标的价格大幅下跌,最大损失也仅限于已支付的权利金。这是买入看涨期权与直接买入股票等资产的根本区别 —— 后者在价格下跌时亏损无上限。
我们将在MQL5中实现期权模拟
图例2显示,(单张买入看涨期权的)δ在0到+1区间内变化。让我们将δ图表的 X 轴从绝对价格转换为点位变动值。方法是用价格减去行权价 —— 本例中即减去31000。转换后的图表如下:
图例3. 行权价31000的看涨期权买入持仓的δ值与标的资产价格相对行权价变动的关系(单张期权)。图中垂直虚线为图表绘制时点。在该时点,δ为0.48。
红线代表当前时点(2025年5月22日),蓝线代表到期时点(2025年6月18日),即布莱克 - 斯科尔斯公式中T=1的情况。
显然,转换后的图表与原图在形态上没有区别,仅X轴刻度不同。由于布莱克 - 斯科尔斯方程需要用到隐含波动率(IV),而我们通常无法获取,更为可行的方案是改用历史波动率(HV)。对于月度期权,使用月度平均HV;周期权则使用周平均HV,依此类推。
为方便对比不同标的资产的图表,并支持在合成工具(由多种资产构成的组合)上构建期权,建议使用相对价格变动,并通过计算出的历史波动率对变动幅度进行归一化处理。
相对价格可由下式表示:相对价格 (price) = (price - 行权价) / HV。
假设我们计算出期权存续期内的HV = 5000,如图例3所示,可以得到如下类型的图表:
图例4. 行权价31000的看涨期权买入持仓的δ值与经标的历史波动率归一化后的相对价格变动关系(单张期权)。图中垂直虚线为图表绘制时点。在该时点,δ为0.48。
红线代表当前时点(2025年5月22日),蓝线代表到期时点(2025年6月18日),即布莱克 - 斯科尔斯公式中T=1的情况。
由此可见,当价格等于行权价时,δ等于0.5。红线所示的δ曲线,正是数学与机器学习中常见的S型函数(Sigmoid),形式为:1 / (1 + exp(-K * (x - S))),其中,S = 0,而K通常未知,需要通过实验确定。本质上,系数K取决于期权剩余到期时间。已知当前标的价格(30800)对应的δ值(0.48),我们可以计算出K值 —— 在当前时点K=2,并会随着期权临近到期而变化(增大)。
为实现模拟,我们将忽略期权的时间衰减以及K值的变化,在整个期权周期内将其视为常数。这对于买入看涨期权的盈亏表现偏有利,而对于买入看跌期权偏不利,但整体上对期权组合的盈亏不会产生显著影响。
如果将S型函数 1 / (1 + exp(-K * (x - S)))中的参数S设为0.5,则δ随相对价格变动的曲线会沿X轴向右平移S个单位,在价格为0处得到一个接近0的值 —— 函数渐近趋近于0和1。这一形式更适合用于模拟与波动率区间归一化,我们在编写MQL的EA时将采用该形式。
至此,我们已具备在MQL5中编写代码、为任意标的资产实现期权模拟的全部原始数据,即:模拟函数方程、行权价、历史波动率,以及对理想布莱克 - 斯科尔斯市场运行逻辑的理解。
- 1. 基类
我们将模拟以下类型的期权:
- 买入标的资产的权利 —— 买入看涨期权(Long Call)
- 卖出标的资产的权利 —— 买入看跌期权(Long Put)
- 售出买入标的资产的权利 —— 卖出看涨期权(Short Call)
- 售出卖出标的资产的权利 —— 卖出看跌期权(Short Put)
将所有支持的期权类型定义为一个枚举类型:
// --------------------------------------------------------------------- // Option type: // --------------------------------------------------------------------- enum ENUM_OPTION_TYPE { ENUM_OPTION_CALL_LONG, // Long Call ENUM_OPTION_CALL_SHORT, // Short Call ENUM_OPTION_PUT_LONG, // Long Put ENUM_OPTION_PUT_SHORT, // Short Put };
该基类继承自MQL内置的标准CObject类。这样设计是为了方便使用MQL内置的CList链表来操作对象,并构建任意复杂程度的期权组合结构。这部分内容将在本系列的下一篇文章中详细说明。
基类的主要成员变量包括:
- 期权类型 —— type_enum字段
- 行权价 —— strike字段
- 期权当前δ值 —— δ字段
- 用于波动率归一化的价格 —— norm_price字段
同时还包含若干辅助字段,供继承自该基类的子类使用。该基类为抽象类,其中GetSigmoidValue方法必须在子类中实现。在布莱克 - 斯科尔斯模型下,对于不同类型的期权,该方法的实现各不相同。这种设计也便于后续扩展其他类型的期权,例如基于机器学习方法或其他定价模型的期权。
// ===================================================================== // The base class of the option is derived from 'CObject' so that it can be // put into the lists. // ===================================================================== class TOptionBase : public CObject { protected: ENUM_OPTION_TYPE type_enum; double strike; double shift; double koeff_K; double koeff_S; int digits; double norm_price; double delta; double norm_koeff; bool range_inited_Flag; public: // --------------------------------------------------------------------- // Constructor: // --------------------------------------------------------------------- TOptionBase(const ENUM_OPTION_TYPE _type, const double _k, const double _s, const int _digits) : CObject(), type_enum(_type), strike(0.0), shift(0.0), koeff_K(_k), koeff_S(_s), digits(_digits), range_inited_Flag(false), delta(0.0), norm_koeff(0.0) { } public: // --------------------------------------------------------------------- // Set the delta normalization range for the central strike: // --------------------------------------------------------------------- void SetRange(const double _strike, const double _norm_price) { this.strike = _strike; this.norm_price = _norm_price; this.norm_koeff = 1.0 / (this.norm_price - this.strike); this.range_inited_Flag = true; } // --------------------------------------------------------------------- // Calculate the normalized delta value for a given price: // --------------------------------------------------------------------- double UpdateDelta(const double _price) { if(this.range_inited_Flag == false) { this.delta = 0.0; } else { this.delta = NormalizeDouble(this.GetSigmoidValue((_price - this.strike) * this.norm_koeff), 5); } return(this.delta); } // --------------------------------------------------------------------- // Get the value of the previously calculated normalized delta: // --------------------------------------------------------------------- double Delta() { return(this.delta); } // --------------------------------------------------------------------- // Get the range initialization flag for the delta: // --------------------------------------------------------------------- bool IsRangeInited() { return(this.range_inited_Flag); } protected: // --------------------------------------------------------------------- // Get the normalized delta value: // --------------------------------------------------------------------- virtual double GetSigmoidValue(const double _x) = 0; }; // ---------------------------------------------------------------------
- 2. 实盘环境下的运行逻辑
在模拟期权时,我们必须设定一个δ变动阈值,达到该阈值时对持仓进行再平衡操作。该阈值一方面会限制模拟的精准度,另一方面可以控制开平仓产生的手续费成本。我们引入两个标的资产相关参数:Vmin_size(最小持仓手数)与Vmin_delta_size(持仓手数最小变动单位)。
同时我们设定:将δ绝对值区间|0.0 ~ 1.0|均匀划分为若干档位,每一档对应一次持仓再平衡。将划分档位数量N设置为外部可调参数。此时总持仓手数区间为:0 ~ Vmin_size + Vmin_delta_size × (N − 1)。
通常来说,Vmin_size与Vmin_delta_size取值相等,但并非强制要求。本文默认将δ区间|0.0 ~ 1.0|划分为10个再平衡档位。
- 3. 买入看涨期权
图例5. 模拟买入看涨期权的δ值与相对价格变动DPrice(表格中变量x)的对应关系,参数:S = 0.5, K = 10。将δ区间等分为10段。
行权价对应相对价格零点DPrice = 0。当δ值接近1时,期权处于“深度实值状态”,此时其表现等同于满仓持有标的资产。随着标的价格向行权价靠拢,持有的标的仓位逐步缩减;当期权变为虚值、δ趋近于0时,标的持仓归零。而真实期权在此阶段会产生等同于权利金规模的亏损。
// =====================================================================
// Long Call type option class
// =====================================================================
class OptionLongCall : public TOptionBase
{
public:
// ---------------------------------------------------------------------
// Constructor:
// ---------------------------------------------------------------------
OptionLongCall(const double _k, const double _s, const int _digits)
:
TOptionBase(ENUM_OPTION_CALL_LONG, _k, _s, _digits)
{
}
protected:
// ---------------------------------------------------------------------
// Get the normalized delta value:
// ---------------------------------------------------------------------
double GetSigmoidValue(const double _x) override
{
return(1.0 / (1.0 + MathExp(-this.koeff_K * (_x - this.koeff_S))));
}
};
// ---------------------------------------------------------------------
- 4. 买入看跌期权
图例6. 模拟买入看跌期权的δ值与相对价格变动DPrice(表格变量x)的对应关系,参数:S = 0.5, K = 10。将δ区间等分为10段。
行权价对应相对价格零点DPrice = 0。当δ值接近-1时,期权处于“深度实值状态”,此时其表现等同于满仓做空标的资产。随着标的价格向行权价靠拢,持有的标的仓位逐步缩减;当期权变为虚值、δ趋近于0时,标的持仓归零。而真实期权在此阶段会产生等同于权利金规模的亏损。
// Long Put type option class
// =====================================================================
class OptionLongPut : public TOptionBase
{
public:
// ---------------------------------------------------------------------
// Constructor:
// ---------------------------------------------------------------------
OptionLongPut(const double _k, const double _s, const int _digits)
:
TOptionBase(ENUM_OPTION_PUT_LONG, _k, _s, _digits)
{
}
protected:
// ---------------------------------------------------------------------
// Get the normalized delta value:
// ---------------------------------------------------------------------
double GetSigmoidValue(const double _x) override
{
return(-1.0 / (1.0 + MathExp(-this.koeff_K * (-_x - this.koeff_S))));
}
};
// ---------------------------------------------------------------------
- 5. 卖出看涨期权
图例7. 模拟卖出看涨期权(空头看涨期权)的δ值随相对价格变动DPrice(表格变量x)的变化关系,参数:S = 0.5, K = 10。将δ区间等分为10段。
行权价对应相对价格零点DPrice = 0。当δ值接近-1时,期权处于“深度实值状态”,此时其表现等同于满仓做空标的资产。随着标的价格向行权价靠拢,持有的标的仓位逐步缩减;当期权变为虚值、δ趋近于0时,标的持仓归零。真实期权此时会产生等同于权利金金额的盈利。
// Short Call type option class
// =====================================================================
class OptionShortCall : public TOptionBase
{
public:
// ---------------------------------------------------------------------
// Constructor:
// ---------------------------------------------------------------------
OptionShortCall(const double _k, const double _s, const int _digits)
:
TOptionBase(ENUM_OPTION_CALL_SHORT, _k, _s, _digits)
{
}
protected:
// ---------------------------------------------------------------------
// Get the normalized delta value:
// ---------------------------------------------------------------------
double GetSigmoidValue(const double _x) override
{
return(-1.0 / (1.0 + MathExp(-this.koeff_K * (_x - this.koeff_S))));
}
};
// ---------------------------------------------------------------------
- 6. 卖出看跌期权
图例8. 模拟卖出看跌期权的δ值随相对价格变动DPrice(表格变量x)的变化关系,参数:S = 0.5, K = 10。将δ区间等分为10段。
行权价对应相对价格零点DPrice = 0。当δ值接近1时,期权处于“深度实值状态”,此时其表现等同于以最大规模持有标的资产的多头敞口。随着标的价格向行权价靠拢,持有的标的仓位逐步缩减;当期权变为虚值、δ趋近于0时,标的持仓归零。真实期权此时会产生等同于权利金金额的盈利。
// Short Put type option class
// =====================================================================
class OptionShortPut : public TOptionBase
{
public:
// ---------------------------------------------------------------------
// Constructor:
// ---------------------------------------------------------------------
OptionShortPut(const double _k, const double _s, const int _digits)
:
TOptionBase(ENUM_OPTION_PUT_SHORT, _k, _s, _digits)
{
}
protected:
// ---------------------------------------------------------------------
// Get the normalized delta value:
// ---------------------------------------------------------------------
double GetSigmoidValue(const double _x) override
{
return(1.0 / (1.0 + MathExp(-this.koeff_K * (-_x - this.koeff_S))));
}
};
// ---------------------------------------------------------------------
- 7. 标的资产持仓手数计算的特殊要点
计算持仓手数时,需要先将δ值从区间|0.0…1.0|转换为整数区间|0…10|,我们再用该整数值乘以最小手数变动单位,得到目标持仓手数。
假设我们将δ划分为10个子区间。如果只是简单地将δ乘以10再取整,是不够严谨的。因为当δ在区间边界附近小幅波动时,持仓手数会随之反复跳动。让我们举个例子说明:当δ为0.090时,手数 = int(0.090 * 10) = 0(无持仓);随后δ上涨到0.101,手数变为int(0.101 * 10) = 1;紧接着下一根K线δ又回落至0.090,手数再次变回0。这就造成了瞬间开仓又平仓的无意义操作,在几秒内可能反复多次,也就是所谓的信号“抖动”(chattering)。
因此,在将δ转换为手数时,必须为持仓再平衡加入“滞后回差”(hysteresis)机制。实现方式是区分再平衡方向,我们根据当前是加仓还是减仓,结合上一次的持仓手数进行判断。
让我们引入期权模拟等级的概念:简单来说,就是建立映射|0.0…1.0| → |0…10|,期权模拟等级取值范围为0至10。等级数量由外部参数N指定。模拟等级与δ一样,采用绝对值计算,同时保留符号 —— {0, +1, -1}。当等级为0时表示空仓。
- 8. 在MQL5中计算标的资产持仓手数
计算当前期权模拟等级时,采用查表法实现“滞后回差”机制,使用两个数组:
- IncreaseLevel_Array —— 等级上调时的判定数组;
- DecreaseLevel_Array —— 等级下调时的判定数组。
在UpdateOptionLevel方法中实现该逻辑,此方法接收两个参数:
- _delta —— 期权当前δ;
- _curr_level —— 上一次计算出的期权模拟等级,首次调用方法时为0。
- _ZeroDelta —— 对应模拟等级0的δ阈值,取值范围为0.001至0.099。
- _LevelsNumber —— 期权模拟等级总数,等级越多,模拟越平滑、精准,但标的资产的交易手数也会更大(受变动粒度限制)。
// ===================================================================== // Class for calculating the contract volume of the underlying asset for an option: // ===================================================================== class OptionContractsVolume { int IncreaseLevel_Array[]; // option emulation level when increasing it int DecreaseLevel_Array[]; // option emulation level when decreasing it // --------------------------------------------------------------------- protected: double ZeroDelta; // delta value corresponding to the zero level of option emulation int LevelsNumber; // number of option emulation levels // --------------------------------------------------------------------- int zero_delta; // integer delta value corresponding to the zero level of option emulation int curr_option_level_sign; // current sign of the option emulation level int curr_option_level; // current option emulation level // --------------------------------------------------------------------- int curr_inc_level; int curr_dec_level; // --------------------------------------------------------------------- int curr_contracts_index; // --------------------------------------------------------------------- bool is_contracts_updated_Flag; public: // --------------------------------------------------------------------- // Constructor: // --------------------------------------------------------------------- OptionContractsVolume(const double _ZeroDelta, const int _levels_number) : ZeroDelta(_ZeroDelta), LevelsNumber(_levels_number), curr_option_level_sign(0), curr_option_level(0), curr_inc_level(0), curr_dec_level(0), is_contracts_updated_Flag(false) { this.zero_delta = (int)(NormalizeDouble(this.ZeroDelta, 3) * 1000.0); // Allocate memory for arrays storing emulation levels: ArrayResize(this.IncreaseLevel_Array, (this.LevelsNumber + 1) * 100 + 1); ArrayResize(this.DecreaseLevel_Array, (this.LevelsNumber + 1) * 100 + 1); // Fill in the array upwards: for(int i = 0; i < this.LevelsNumber + 1; i++) { ArrayFill(this.IncreaseLevel_Array, i * this.LevelsNumber * 100, this.LevelsNumber * 100, i); } ArrayFill(this.IncreaseLevel_Array, (this.LevelsNumber + 1) * 100, 1, this.LevelsNumber); // Fill in the array downwards: ArrayFill(this.DecreaseLevel_Array, 0, zero_delta, 0); ArrayFill(this.DecreaseLevel_Array, zero_delta, this.LevelsNumber * 100 - zero_delta + 1, 1); for(int i = 2; i < this.LevelsNumber + 1; i++) { ArrayFill(this.DecreaseLevel_Array, i * this.LevelsNumber * 100, this.LevelsNumber * 100, i); } ArrayFill(this.DecreaseLevel_Array, (this.LevelsNumber + 1) * 100, 1, this.LevelsNumber); } // --------------------------------------------------------------------- // Calculate the option emulation level for a given delta: // --------------------------------------------------------------------- // - if the level has changed, return 'true'. // --------------------------------------------------------------------- bool UpdateOptionLevel(const double _delta, const int _curr_level) { this.is_contracts_updated_Flag = false; // Define trade direction: int delta_int = (int)(NormalizeDouble(_delta, 3) * 1000.0); this.curr_option_level_sign = 0; if(delta_int > zero_delta / 2) { this.curr_option_level_sign = 1; } else if(delta_int < -zero_delta / 2) { this.curr_option_level_sign = -1; } // Current index for arrays, based on 'Delta' with 3 decimal places: this.curr_contracts_index = (int)MathAbs(delta_int); if(this.curr_contracts_index > ((this.LevelsNumber + 1) * 100)) { // The index should not exceed the array size (here the option is deep in the money): this.curr_contracts_index = (this.LevelsNumber + 1) * 100; } // Current option emulation level (0...N) in the direction of INCREASE: this.curr_inc_level = this.IncreaseLevel_Array[this.curr_contracts_index]; // Current option emulation level (0...N) in the direction of DECREASE: this.curr_dec_level = this.DecreaseLevel_Array[this.curr_contracts_index]; // If the option emulation level (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; return(true); } // If the option emulation level (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; return(true); } return(false); } }; // ---------------------------------------------------------------------
完整代码可参见下方附件OptionEmulatorA1.mqh。
结论
本文介绍了通过标的资产实现期权模拟的方法。这是一种相对复杂但功能强大的工具,具备以下特点:
- 可以构建复杂、非标准的期权策略;
- 相比标准场内期权,灵活性更高;
- 要求使用者对数学模型有深入理解;
- 在合理风控下,具备较高的使用效率。
该方法在以下场景中尤为实用:
- 大型投资组合的对冲操作;
- 构建特殊期权策略;
- 在期权流动性不足的市场中进行交易。
在下一篇文章中,我们将进入实操部分 —— 利用MQL5交易函数实现标的资产持仓的维护与再平衡。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/18131
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
从基础到中级:结构(七)
新手在交易中的10个基本错误








非常感谢这篇科普!等手头宽裕些,我会把期权纳入交易策略……
谢谢。顺便说一句,这直接关系到你的主题——点差交易。我现在在“三号”上测试的,正是期权组合策略。
谢谢。顺便说一下,这与您讨论的话题——点差交易——直接相关。我目前在“三号”上测试的,正是这种期权结构。
明白了。好像是利用期权波动率表面——这只是我之前在某处读到的……)
罗曼,这里的“表面”完全无关。实际上。
据我理解,其核心在于:当价格朝有利方向波动时,会持续向总头寸追加仓位;而当价格朝不利方向波动时,则减少头寸。这适用于买入看跌期权和看涨期权。
但据我理解,除了与佣金相关的间接费用外,行权成本还将远高于购买期权时的成本,尤其是当期权在购买时远未处于实值状态时。 此外,为了实现平滑的再平衡,需要相当大的虚拟期权规模,因为仅靠1-10的步长根本无法以可接受的精度进行模拟。
优化问题在于,针对特定金融工具,哪种价格调整步长最为有利。
最好能针对实际期权与虚拟期权进行实际成本比较,分别考察到期时处于实值和未处于实值的两种情况。
据我理解,其核心在于:当价格朝有利方向波动时,会持续向总头寸追加仓位;而当价格朝不利方向波动时,则会减少头寸。这适用于买入看跌期权和看涨期权。
但据我理解,除了与佣金相关的间接费用外,行权成本还将远高于购买期权时的成本,尤其是当期权在购买时远未达到行权价时。 此外,为了实现平滑的再平衡,需要相当大的虚拟期权规模,因为仅靠1-10的步长根本无法以可接受的精度进行模拟。
优化问题在于,针对特定金融工具,哪种价格调整步长最为有利。
最好能针对实际期权与虚拟期权的成本进行实际比较,分别考虑在到期时处于实值和未处于实值的情况。
步数由BA参数决定——最小手数/最小手数变动量。无法随意设定。
通常情况下,GO值会更高。但这并不重要——如果可以轻松买卖现成的期权,那么模拟就毫无意义。这里的选择是:要么完全不交易期权,要么通过模拟进行交易。 再次强调,这仅适用于那些原则上没有期权的BA。