SIMULATION · PERFORMANCE

Optopsy 的 simulate():从逐笔交易到权益曲线

默认的策略函数只给你按区间分组的统计。要拿到「一笔一笔按时间推进、有资金约束」的结果,要用 simulate()——它输出交易明细、权益曲线与一整套绩效指标。

  • 三个输出trade_log(逐笔)、equity_curve(按出场日索引)、summary(绩效汇总)。
  • selector 决定「同一入场日选哪一笔」:四选一或传自定义函数。
  • max_positions 控制并发:为 1 时是不重叠的贪心策略。
  • 12 个风险指标:有明确口径,也有明确的空数据返回值。
调用策略拿逐笔raw=True 取到单笔交易,不直接用分组统计
归一化成统一交易表含入场日 / 出场日 / 到期日 / 入场成本 / 出场收入 / 收益率
按日期推进 + 仓位约束按入场日排序,按 max_positions 过滤重叠持仓
产出三件套trade_log / equity_curve / summary
依据 optopsy/simulator.py 的实现绘制的示意图;非官方流程图,不代表任何收益结果。

SIGNATURE

Optopsy simulate() 参数表:每个参数改变什么

先看 Optopsy 的签名,再逐项理解。所有策略参数(DTE、Delta、滑点、手续费等)都通过 **strategy_kwargs 原样透传给策略函数。

参数类型 / 默认作用改变它会怎样适用场景注意点
dataDataFrame期权链数据直接影响候选合约必填与策略函数要求同一套列契约
strategy可调用对象要模拟的策略函数换策略即换整条结果必填传函数本身(如 op.short_puts),不要传调用结果
capitalfloat,默认 100000.0起始资金影响可用仓位与 summary 中的资金相关指标想模拟小资金账户或大账户时资金耗尽(权益跌至 0 及以下)时交易日志会被截断
quantityint,默认 1每笔合约数量放大每笔盈亏的绝对额想模拟多张合约multiplier 一起决定名义规模
max_positionsint,默认 1并发持仓数上限从「不重叠的单笔」变成「可同时持有 N 笔」想同时铺多个到期日或分批建仓时为 1 时按入场日 ≥ 上一笔出场日做贪心不重叠过滤
multiplierint,默认 100合约乘数改变每笔的现金影响股指期权通常是 100不同市场的乘数不同,按你的标的确认真实值
selectorstr 或可调用,默认 'nearest'同一入场日有多笔候选时选哪一笔直接改变模拟的交易序列想对比不同选仓规则的影响时可选 nearest / highest_premium / lowest_premium / first,也可传自定义函数
**strategy_kwargs透传策略参数(DTE、Delta、滑点、手续费、早退等)与直接调用策略时一致所有场景参数校验同样走 Pydantic,类型必须严格匹配

依据 optopsy/simulator.pysimulate() 签名与官方 examples 文档整理。

注意 strategy 传的是函数对象而不是调用结果。写成 simulate(data, op.short_puts(...)) 是常见错误——要先传函数,参数走 kwargs。

RUN IT

Optopsy 怎么跑出首张权益曲线:四步方法

每一步都给可复制代码与预期输出。第四步是本站建议的核对动作。

写法取自官方 examples 文档;本站未在 Python 3.12+ 环境实跑,属源码级核验。

  • 准备数据与参数
    import optopsy as op
    
    data = op.csv_data("SPX_2023.csv")
    
    result = op.simulate(
        data,
        op.short_puts,
        capital=100_000,
        quantity=1,
        max_positions=2,
        multiplier=100,
        selector="nearest",
        max_entry_dte=45,
        exit_dte=14,
    )

    selector="nearest" 表示同一入场日有多笔候选时,选最接近平值的那一笔(实现上按 Delta 最接近 0.50 判断)。其余策略参数与直接调用策略时写法一致。

    预期输出:result 是一个 SimulationResult 对象

  • 看绩效汇总
    print(result.summary)

    summary 是一个扁平字典,既含基础交易统计(笔数、胜率、盈亏比等),也含从 metrics 模块算出的风险调整指标(夏普、索提诺、VaR、CVaR、Calmar 等)。

    预期输出:包含 win rate、profit factor、回撤幅度与风险调整类指标

  • 看逐笔交易明细
    print(result.trade_log.head())

    交易日志每行一笔已完成交易,含入场日、出场日、到期日、入场成本、出场收入、收益率与 exit_type。这是核对「模拟到底做了什么」的主要依据。

    预期输出:列里可看到入场/出场日期与现金流字段

  • 核对权益曲线的首尾(本站建议)
    eq = result.equity_curve
    print(eq.head(), eq.tail())
    # 曲线按“出场日”索引;每笔平仓后记录当时的权益值

    权益曲线按出场日索引,不是每个自然日都有一点。如果你需要逐日曲线,要先按日重采样。曲线的起始值反映了开头那笔交易平仓后的权益。

    预期输出:序列按时间递增,最后一个值即最终权益

  • selector 的四种取值

    nearest:最接近平值(Delta 最接近 0.50);highest_premium:选收到的权利金偏高的那笔;lowest_premium:选付出成本最低的那笔;first:直接取候选列表里的那一条,用于可复现的确定性测试。

    仓位约束怎么生效

    max_positions=1 时,下一笔的入场日必须不早于上一笔的出场日(贪心不重叠)。大于 1 时会跟踪未平仓区间,允许同时持有若干笔。这决定了同一份数据能产生多少笔交易。

    资金耗尽会怎样

    当权益跌到 0 或以下,交易日志会在那一笔被截断——这是一种「破产」处理,而不是继续加杠杆。所以曲线末尾突然变短是有含义的,不要当成数据缺失。

    OUTPUT CONTRACT

    Optopsy 三件套输出:每一列/每个键代表什么

    把这三样东西的分工搞清楚,就能判断一个结果该不该信。

    输出结构关键字段 / 键用途注意点
    trade_logDataFrame,一行一笔入场日、出场日、到期日、标的、入场成本、出场收入、收益率、exit_type核对模拟细节;自己算任意自定义指标入场成本与出场收入是带符号的现金流,不是价格
    equity_curve按出场日索引的序列每个出场日对应的权益值画资金曲线;算回撤幅度(基于权益而非收益率)不是逐日序列;需要逐日要先重采样
    summary扁平字典交易统计 + 风险调整指标快速比较不同参数或不同策略指标口径见下一节;空数据或标准差为 0 时部分指标返回 0
    SimulationResult数据类包含上面三者统一承载模拟结果注意与 PortfolioResult 区分
    PortfolioResult数据类trade_log(含 leg 列)、equity_curvesummaryleg_results组合模拟的返回对象组合日志额外有 leg 列用来区分来源腿

    依据 optopsy/simulator.py 中两个结果数据类的 docstring 与官方 examples 文档整理。

    组合模拟simulate_portfolio(legs, capital) 接收一个腿的列表,每条腿指定自己的数据、策略、权重与参数,各自独立跑一遍再合并。合并时每条腿的权益曲线会用自己分配到的起始资金播种,这样「还没开仓的空闲现金」也会体现在组合总额里——否则组合曲线会在早期被低估。

    METRIC DEFINITIONS

    Optopsy 12 个风险指标说明:官方文档没有集中列过的口径

    这些指标可以单独调用,也由 simulate() 自动算进 summary。口径按源码文档整理。

    函数含义与口径空数据 / 边界情况返回什么适用场景注意点
    sharpe_ratio年化夏普比率,默认年化因子 252、无风险利率 0标准差为 0 或数据为空时返回 0.0跨策略比较风险调整后收益默认无风险利率为 0,与实务口径可能不同
    sortino_ratio年化索提诺比率,只惩罚下行波动同上期权收益分布不对称时比夏普更合适源码注释明确说明它对不对称收益更合适
    max_drawdown基于权益曲线的回撤幅度,返回负数曲线为空或单调上升时返回 0.0评估资金曲线的痛感输入是绝对权益值,不是收益率
    max_drawdown_from_returns基于收益率序列算回撤幅度,返回负数空数据返回 0.0手上只有逐笔收益率时initial_capital 参数保留兼容,实际不参与计算
    value_at_risk历史 VaR:给定置信度下的分位点。默认 0.95 即取第 5 百分位空数据返回 0.0衡量极端亏损的量级返回值通常为负数
    conditional_value_at_riskCVaR / 期望损失:低于 VaR 那部分收益的均值空数据返回 0.0比 VaR 更关注尾部同样通常为负
    win_rate盈利笔数占比,返回 0–1空数据返回 0.0最直观的胜率胜率高不等于盈利,要结合盈亏比看
    profit_factor总盈利 / 总亏损的绝对值无亏损时返回 inf;无盈利或空数据返回 0.0衡量盈亏结构出现 inf 时不要直接放进图表,先处理
    calmar_ratio年化收益 / 回撤幅度回撤幅度为 0 或数据不足时返回 0.0关注回撤的效率指标既涉及年化又涉及回撤,两处口径都要一致才好比较
    omega_ratioOmega 比率:相对阈值收益的概率加权盈亏比,>1 表示收益占优空数据返回 0.0想看整个收益分布而非只均值方差对阈值的选择敏感
    tail_ratio右尾(95 分位)/ 左尾(5 分位)之比,>1 表示右尾更厚空数据返回 0.0期权策略的尾部特征尤其重要分位数在样本很少时不稳定
    compute_risk_metrics一次性算齐上面各项,返回扁平字典按各子函数各自的规则想一次拿全指标可同时传 equity,否则回撤类指标从收益率推导

    依据 optopsy/metrics.py 各函数的 docstring(含默认参数与边界返回值)整理;指标计算委托给 empyrical-reloaded 库。

    比较指标前先对齐三件事:样本区间、年化因子(默认 252)、无风险利率(默认 0)。否则两个数字看起来可比,其实口径不同。profit_factor 返回 inf 是正常情况下就会发生的(某段区间没有亏损笔),做图表时要单独处理。

    READING THE RESULT

    Optopsy 结果怎么读:五个容易被误读的地方

    1. 分组统计不等于模拟结果

    默认策略输出是按 DTE 与 Delta 区间分组的统计,区间之间互相独立;simulate() 才引入了时间顺序与资金约束。同一个策略的「平均收益率」和「模拟出的资金曲线」本来就不会一致。

    2. equity_curve 是按出场日索引的

    不是逐日序列。如果你的分析需要逐日风险指标(如逐日波动率),必须先在日期轴上重采样,否则会把间隔不均的点当成等间隔序列。

    3. 回撤来自权益而非收益率

    两个函数一个吃权益、一个吃收益率,返回的都是负数。混用会得到量级完全不同的数字。

    4. 交易笔数受 max_positions 影响

    同一份数据,max_positions 从 1 改成 3,交易笔数可能显著增加。比较不同参数的结果前,先确认笔数差异是不是来自这个参数。

    你想回答的问题该用哪个输出别用哪个为什么
    这笔策略在历史上平均赚多少raw=True 的逐笔收益率分布只看 summary 里的单一均值分布往往高度不对称,均值会掩盖尾部
    按 45 DTE 入场是否比按 30 DTE 更好两种参数的策略输出分组统计对比不同参数下的 summary 直接比分组统计能按 DTE 分桶看差异,越过了分桶细节
    资金曲线承受了多大回撤equity_curve + max_drawdownmax_drawdown_from_returns 代替两者输入语义不同,且都返回负数易混淆
    多策略组合是否更稳simulate_portfolio() 的组合曲线与腿级结果把各策略的 summary 手工平均组合有资金分配与并发效应,平均值不代表组合
    某笔交易为什么亏trade_log 单笔行的入场/出场与 exit_type回看策略的分组统计分组统计里没有单笔信息
    结果是否对未来有参考意义本页以外的判断框架任何回测数字官方免责声明明确:历史结果不代表未来,且未覆盖全部现实因素

    依据官方 examples 文档的输出说明、simulator 与 metrics 模块的源码实现整理。

    FAQ

    Optopsy 模拟与绩效常见问题

    simulate() 和直接用策略函数有什么区别?

    直接调用策略函数得到的是按 DTE 与 Delta 区间分组的统计,各区间彼此独立;simulate() 会按时间顺序逐笔推进,并引入起始资金、合约数量、并发仓位上限与权益曲线。前者回答「这批交易长什么样」,后者回答「如果按时间一笔笔做,资金会怎么走」。

    selector 该选哪个?

    取决于你想检验什么。nearest(默认,选最接近平值的一笔)是最中性的起点;highest_premiumlowest_premium 用来检验「选贵的还是选便宜的」对结果的影响;first 用于需要严格可复现的测试场景。也可以传自定义函数实现自己的选仓规则。

    max_positions 设成 1 和设成 3 差别大吗?

    通常差别很大。为 1 时要求下一笔的入场日不早于上一笔的出场日,等于强制交易不重叠;设大之后允许同时持有若干笔,交易笔数会增加,曲线形态也会变。比较不同参数的结果前,先确认笔数差异是不是来自这个参数。

    为什么 profit_factor 会显示为无穷大?

    当统计区间内没有任何亏损交易时,总亏损为 0,源码明确在这种情况下返回 inf。这是正常的边界行为,不是错误。做图表或排序时要单独处理这个值。

    权益曲线为什么不是每天都有一个点?

    equity_curve出场日索引,只有交易平仓时才会记录一个权益值。需要逐日序列时,要自己在日期轴上重采样。以源码实现为准。

    模拟结果是负数就说明策略不行吗?

    不一定,但要非常谨慎地解读。结果受数据区间、成交假设(滑点、手续费)、max_positionsselector 的共同影响。正确做法是先固定除一个变量外的所有条件,再看单个变量带来的差异。回测结果不构成任何投资建议,官方免责声明也明确历史结果不代表未来。

    simulate_portfolio() 的权重是怎么用的?

    每条腿按自己的 weight 分到一部分起始资金,然后各自独立跑一次模拟;组合权益曲线由各腿曲线按日相加得到,并且每条腿的曲线会用自己的起始资金播种,以便把「还没开仓的空闲现金」也计入组合总额。组合交易日志额外带一个 leg 列来区分来源。

    下一步:怎么把成交假设调到更接近真实

    早退与成本页讲清 stop_loss / take_profit / max_hold_days 的符号约束与优先级、手续费结构,以及四种滑点模式的差异与 fill_ratio 的反直觉方向。