English Русский Español 日本語 Português
preview
不使用期权的期权交易(第一部分):基础理论与基于标的资产的模拟实现

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

MetaTrader 5交易 |
19 10
Dmitriy Skub
Dmitriy Skub

引言

期权这一金融工具正受到越来越多市场参与者的关注。仅举一例:在过去几年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) —— 标准正态分布累积分布函数;
      • σ —— 标的资产波动率。

      该模型基于若干假设:

      1. 市场完全有效,不存在套利空间;
      2. 无风险利率恒定且已知;
      3. 标的资产波动率恒定;
      4. 模型假设不考虑股息支付(该模型后续已优化);
      5. 资产价格服从对数正态分布,价格不可能为负;
      6. 时间连续,交易不间断;
      7. 无交易佣金与税费。

      由上述假设可推导出模型的主要缺陷:

      • 市场并非完全有效,只有流动性极高的品种在一定程度上接近这一状态;
      • 标的资产波动率频繁且持续变化,无法视为常数;
      • 实际分布远非对数正态,真实市场存在“厚尾”特征(崩盘风险),而模型忽略了价格的突发性波动;
      • 不适用于美式期权定价。

      目前已有可纳入上述部分因素的替代期权定价模型:

      • 赫斯顿模型(随机波动率);
      • 贝叶斯模型(纳入宏观因子);
      • 机器学习方法(神经网络预测期权价格)。

        • 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。
        OptionContractsVolume类的构造函数接收两个参数:
        • _ZeroDelta —— 对应模拟等级0的δ阈值,取值范围为0.001至0.099。
        • _LevelsNumber —— 期权模拟等级总数,等级越多,模拟越平滑、精准,但标的资产的交易手数也会更大(受变动粒度限制)。
        构造函数会填充IncreaseLevel_Array和DecreaseLevel_Array,并初始化各类辅助变量(详见带注释的源代码)。
        // =====================================================================
        //      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

        附加的文件 |
        最近评论 | 前往讨论 (10)
        Dmitriy Skub
        Dmitriy Skub | 5 6月 2025 在 03:50
        Roman Shiredchenko #:
        非常感谢这篇科普!等手头宽裕些,我会把期权纳入交易策略……
        这篇文章太棒了!我正在深入研究这个话题。期待您能进一步深入探讨期权结构的特点以及交易案例!!!包括表面期权波动率的应用……)如果我没理解错的话,确实有这种概念……)

        谢谢。顺便说一句,这直接关系到你的主题——点差交易。我现在在“三号”上测试的,正是期权组合策略。

        Roman Shiredchenko
        Roman Shiredchenko | 5 6月 2025 在 04:00
        Dmitriy Skub #:

        谢谢。顺便说一下,这与您讨论的话题——点差交易——直接相关。我目前在“三号”上测试的,正是这种期权结构。

        明白了。那似乎是利用期权波动率曲面——我只是在某处读到过……)
        哪些被高估了可以卖出…… 这只是供参考……
        Dmitriy Skub
        Dmitriy Skub | 5 6月 2025 在 04:49
        Roman Shiredchenko #:
        明白了。好像是利用期权波动率表面——这只是我之前在某处读到的……)
        哪些被高估了,可以卖出…… 这只是为了了解情况……

        罗曼,这里的“表面”完全无关。实际上。

        Aleksey Vyazmikin
        Aleksey Vyazmikin | 4 7月 2025 在 20:00

        据我理解,其核心在于:当价格朝有利方向波动时,会持续向总头寸追加仓位;而当价格朝不利方向波动时,则减少头寸。这适用于买入看跌期权和看涨期权。

        但据我理解,除了与佣金相关的间接费用外,行权成本还将远高于购买期权时的成本,尤其是当期权在购买时远未处于实值状态时。 此外,为了实现平滑的再平衡,需要相当大的虚拟期权规模,因为仅靠1-10的步长根本无法以可接受的精度进行模拟。

        优化问题在于,针对特定金融工具,哪种价格调整步长最为有利。

        最好能针对实际期权与虚拟期权进行实际成本比较,分别考察到期时处于实值和未处于实值的两种情况。

        Dmitriy Skub
        Dmitriy Skub | 15 7月 2025 在 10:26
        Aleksey Vyazmikin #:

        据我理解,其核心在于:当价格朝有利方向波动时,会持续向总头寸追加仓位;而当价格朝不利方向波动时,则会减少头寸。这适用于买入看跌期权和看涨期权。

        但据我理解,除了与佣金相关的间接费用外,行权成本还将远高于购买期权时的成本,尤其是当期权在购买时远未达到行权价时。 此外,为了实现平滑的再平衡,需要相当大的虚拟期权规模,因为仅靠1-10的步长根本无法以可接受的精度进行模拟。

        优化问题在于,针对特定金融工具,哪种价格调整步长最为有利。

        最好能针对实际期权与虚拟期权的成本进行实际比较,分别考虑在到期时处于实值和未处于实值的情况。

        步数由BA参数决定——最小手数/最小手数变动量。无法随意设定。

        通常情况下,GO值会更高。但这并不重要——如果可以轻松买卖现成的期权,那么模拟就毫无意义。这里的选择是:要么完全不交易期权,要么通过模拟进行交易。 再次强调,这仅适用于那些原则上没有期权的BA。

        交易策略 交易策略
        各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
        从基础到中级:结构(七) 从基础到中级:结构(七)
        在今天的文章中,我们将展示如何着手解决与构建不同元素以及创造更简单、更具吸引力的解决方案相关的问题。尽管内容以学习为导向,因此并不构成生产代码,但深入理解这里将涵盖的概念和知识至关重要。这样,今后我们就能理解我们将展示的代码。
        新手在交易中的10个基本错误 新手在交易中的10个基本错误
        新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
        交易中的神经网络:面向自适应智能体行为的技能层次结构(完结篇) 交易中的神经网络:面向自适应智能体行为的技能层次结构(完结篇)
        本文探讨了HiSSD框架在算法交易任务中的实际实现。同时阐述了如何利用技能层级结构与自适应架构,以构建更具稳健性和可持续性的交易策略。