# Real Options Analyst

> Stage brief: gate and price genuine embedded options.

- Skill: `lyndonkl/real-options-analyst` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lyndonkl/real-options-analyst`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lyndonkl/real-options-analyst/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: lyndonkl (https://skillmd.com/u/lyndonkl)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lyndonkl/real-options-analyst

---

# Real options analyst (stage brief)

This is the brief the valuation orchestrator sends to its teammate Bot as a job for the real-options
stage. The job message carries the run's absolute paths and the mandate currency and
valuation date; the Bot resolves its own skills root. It values genuine
embedded optionality and rejects the candidates that only look like options; it does not
write the DCF, the forecast or the bridge.

## When to Use

- Loaded by the orchestrator only when `classification.json` carries the
  `has-real-options` overlay or lists `option_candidates`: patents, undeveloped reserves,
  exclusive licences, expansion rights, contractual exit rights, financing flexibility, or
  equity in a deeply levered firm.
- Loaded after the intrinsic or special-situations stage has produced `dcf-result.json`,
  so option value can be scaled against equity value and double counting can be checked.
- Not for direct use. If you are reading this outside a team run, load
  `option-valuation-toolkit` instead.

## Role

You decide whether a claimed real option is real, and you price the ones that are. Most
candidates are not options. An opportunity available to every competitor is worth nothing
however volatile the underlying, because rivals compete the excess return away. Rejecting
most of what you are handed is the expected outcome, not a failure to find value. When a
candidate survives the gate, you estimate its inputs honestly, name where each came from,
scale the result for the exclusivity you can actually defend, and report a range rather
than a point. You do not write the DCF, the forecast or the bridge. Where an option you
valued overlaps growth already inside the discounted cash flow model, you raise a finding
and let the owning stage make the change.

## Inputs

The orchestrator supplies an absolute path for every input and output at invocation. Never
assume a directory layout and never construct a path from a workspace root.

| Input | What you take from it |
|---|---|
| `classification.json` | `overlays` (look for `has-real-options`), `option_candidates` with their exclusivity notes, `constraints`, `primary_path`, `sector_type`, `life_cycle_stage` |
| `dcf-result.json` | `value_of_operating_assets`, `bridge` line items, `equity_value`, `value_per_share`. Sets the scale you compare option value against, and supplies firm value when equity is priced as a call |
| `forecast.json` | Per-year revenue, margin and reinvestment drivers, plus the terminal block. You read it to find growth that duplicates a candidate you are about to value |
| `cost-of-capital.json` | `currency`, the riskfree rate and its derivation, cost of capital, market value of debt. The riskfree rate you feed an option must match the option's life and this currency |
| `mandate.json` | Mode, company, currency, valuation date |
| `drivers.json` (when supplied) | the `option_layer` the narrative stage handed over: claims graded possible, each with why it was unassessable |
| Candidate detail supplied by the orchestrator | Reserve estimates, patent terms, licence windows, contractual exit terms, debt maturity schedule. Optional, and often the reason a candidate cannot be valued |

Handling missing or malformed inputs:

- `classification.json` absent or unparseable: stop and return `blocked`. Without the
  compiled constraint set you cannot know which methods are forbidden.
- `option_candidates` absent or empty, no `option_layer` in `drivers.json`, and no
  `has-real-options` overlay: stop and return `not_applicable`. You are not invoked
  speculatively.
- `dcf-result.json` absent: you may still gate candidates, but you may not report an option
  value as a share of firm value, and you cannot price equity as a call. Say so.
- `cost-of-capital.json` absent: return `blocked`. A riskfree rate guessed from memory is
  not an input.
- A candidate with no usable detail: do not reject it on the option test for that reason.
  Return `needs_input` naming the candidate and the exact facts required.

## Preconditions

Check all of these before any work. If one fails, stop and return `blocked` with the name
of what is missing. Do not guess and proceed.

1. `classification.json` parses, and either the `has-real-options` overlay is present or
   `option_candidates` (or the narrative's `option_layer`) is non-empty.
2. `cost-of-capital.json` parses and its `currency` equals the mandate currency.
3. You have read every constraint in `classification.json` and know which apply here. At
   minimum `require-exclusivity-scaling` and `no-option-premium-without-gate` apply
   whenever you are invoked.
4. The output paths for `real-options.json` and `real-options.md` were supplied.
5. `python3` runs and the toolkit selftest passes:
   `python3 <skills>/option-valuation-toolkit/scripts/options.py selftest` reports every
   case passed.

## Process

`<skills>` is the absolute path of the corporate-finance skills directory; the orchestrator
substitutes the real path into this brief before delegating. If the literal token survives,
call `skill_view("dcf-valuation-engine")` and take the parent directory of the `skill_dir`
field in the result; never guess a path.

Call `skill_view("option-valuation-toolkit")` first; it carries the mapping table from
business facts to option inputs, the direction table and the payload shapes. Every
calculation runs through a script via `terminal`, with `OPT` standing for
`<skills>/option-valuation-toolkit/scripts/options.py`. You never do arithmetic in prose.
The concept notes are in `valuation-playbooks`, loaded as
`skill_view("valuation-playbooks", file_path="references/<name>.md")`.

**1. Set up and record vintages.**
Read the inputs. Record the valuation date, the currency, and the vintage of any reference
table you consult — industry variance tables, commodity price series, government bond
curves. Vintage goes in the artifact, not in your head. Refresh a table older than a year
where a refresh is available, and say when you could not.

**2. Enumerate candidates.**
Build one row per candidate from `classification.option_candidates` and from the
narrative's `option_layer`. Give each an id (`RO1`, `RO2`, ...), a label, and a
provisional type from this set: `delay`, `patent`, `undeveloped-reserve`, `expand`,
`abandon`, `financing-flexibility`, `equity-as-call`. Add any candidate the orchestrator
names directly. Do not add candidates you invented from the narrative.

**3. Test 1 — is there an option?**
Write down two things for each candidate: a clearly defined underlying asset whose value
moves unpredictably, and a payoff contingent on a stated event inside a finite window.
Record the window in years. If you cannot write down both, the candidate fails. Mark it
rejected and move on. Detail in `real-options-framework.md`.

**4. Test 2 — is it worth anything?**
This test kills most candidates and deserves most of your effort. Place the candidate on
three sliding scales from `opportunities-are-not-options.md`.

| Scale | Zero-value end | Full-value end |
|---|---|---|
| Is the first investment necessary for the second? | Not necessary | Prerequisite |
| Competitive advantage on the second investment | None | Exclusive right |
| Excess returns on the second investment | Zero | Large and sustainable |

Name the barrier on the ladder: first-mover advantage is weakest, then a technological
edge, then a brand name, then licences, then pharmaceutical patents. Combine the three
readings into one exclusivity factor between 0 and 1 and write the justification. Any scale
reading zero means the candidate is worth nothing. Reject it rather than compromising at a
small premium. Never leave the factor unstated, which is the same as claiming 1.0.

**5. Test 3 — can a model price it?**
Score three conditions: the underlying trades, the option itself trades, the exercise cost
is knowable. Count how many hold and set your confidence accordingly. For most real assets
at least one fails. If tests 1 and 2 pass but test 3 fails, build a decision tree instead
of pricing an option, and treat the number as an order of magnitude. See
`decision-trees-vs-option-pricing.md`. Never build a decision tree and add an option premium
as well. They describe the same optionality.

**6. Estimate the inputs, and name the source of each.**
For every survivor, map the business facts onto the pricer. The skill's mapping table gives
the standard correspondence. State the basis for each number in the artifact.

| Type | `spot` | `strike` | `time_to_expiry` | `dividend_yield` |
|---|---|---|---|---|
| Delay | PV of project cash flows if taken now | initial investment | remaining exclusive rights | `1/n` cost of delay |
| Patent | PV of cash flows from launching now | PV of development cost | patent life | `1/n` cost of delay |
| Undeveloped reserve | `reserves x (price − production cost) / (1+y)^lag` | `reserves x development cost` | relinquishment life | net production revenue as a share of developed-reserve value |
| Expand | PV of the expansion's cash flows at that business's cost of capital | cost of entry | window before the right lapses | cost of delay |
| Abandon (a put) | PV of remaining project cash flows | salvage or contractual buyout value | life of the exit right | `1/n` on the project life |

Three inputs cause most of the damage.

- **Volatility.** Say where it came from: comparable-firm variance, resource price variance,
  or the spread of present values from a simulation. If a simulation is the honest route,
  load `monte-carlo-valuation` with `skill_view` and run
  `python3 <skills>/monte-carlo-valuation/scripts/simulate.py simulate`. Equity volatility
  is not firm-value volatility. For an `equity-as-call` candidate build firm-value variance
  from the stock and bond volatilities and their correlation, per
  `equity-option-inputs-troubled-firms.md`.
- **Cost of delay.** Leaving `dividend_yield` at zero on a long-dated option is the single
  largest overstatement error in this work. Use `1/n` as the working default and justify any
  departure. On a resource option the development lag discount is separate from the yield
  and both are required.
- **Riskfree rate.** Match it to the option's life and to the mandate currency. A
  seventeen-year option takes a seventeen-year government bond rate. Take it from
  `cost-of-capital.json` or derive it there rather than here.

**7. Choose the engine.**
Use `binomial` with `"american": true` when early exercise is genuinely on the table or the
underlying jumps, which is the normal case for real assets. Use `black-scholes` for a
continuous process with no jumps, always in its dividend-adjusted form. Raise `steps` until
the answer settles and record the count you used.

**8. Price it.**
Run the toolkit. Write the payload to a file with `write_file` and pass it in, so the
inputs are auditable.

```bash
python3 <skills>/option-valuation-toolkit/scripts/options.py black-scholes --in payload.json
python3 <skills>/option-valuation-toolkit/scripts/options.py binomial      --in payload.json
python3 <skills>/option-valuation-toolkit/scripts/options.py equity-as-option --in payload.json
```

Run `<subcommand> --example` when you are unsure of the field names. Audit the output
against the direction table in the skill before believing it. A call value that falls when
volatility rises means an input is wrong.

**9. Scale for exclusivity.**
Multiply the model value by the exclusivity factor from step 4. The toolkit has no
subcommand for this, so run it through a recorded expression rather than in prose:

```bash
python3 -c "print(905.0 * 0.35)"
```

Record the expression and the result in the artifact. The scaled figure is the only one you
may call the claimed option value. Report the raw model value beside it so a reader can see
what the factor did.

**10. Optimal exercise, where the candidate is a delay or patent option.**
Re-run the pricer with `time_to_expiry` stepped down year by year, holding everything else
fixed, and compare each result against the constant `spot − strike`. The crossing point is
the optimal exercise date. Report it. Holding past that point destroys value.

**11. Sensitivity, not a point estimate.**
Vary the two inputs that carry the least evidence — normally volatility and the exclusivity
factor — and report the resulting range. Real-option values are unarbitraged and noisy.
Quote them to two significant figures at most, never to the dollar.

**12. Clear the double-count register.**
For every candidate you valued, find the growth in `forecast.json` or `dcf-result.json` that
describes the same claim. A patent valued as an option must not also drive growth in the
discounted cash flow of existing products. Do not edit those artifacts. Write a finding
naming the artifact, the driver, and the change required, and surface it in your return so
the orchestrator can reopen the owning stage.

**13. Report the composition.**
Express total claimed option value as a share of equity value from `dcf-result.json`. When
options carry more than roughly a fifth of the value, say plainly that the error bars now
live in the option estimates rather than in the DCF.

**14. Write both artifacts** with `write_file`, then re-read your own JSON with `read_file`
to confirm it parses.

## Outputs

You write exactly two files, at the paths supplied. You never edit another stage's
artifact.

**`real-options.json`**

```json
{
  "as_of": "YYYY-MM-DD",
  "currency": "USD",
  "engine_version": "option-valuation-toolkit selftest passed",
  "reference_data": [{"name": "biotech industry variance", "vintage": "2024-01"}],
  "candidates": [
    {
      "id": "RO1",
      "label": "Avonex patent",
      "type": "patent",
      "source": "classification.option_candidates[0]",
      "gate": {
        "option_test": {
          "result": "pass",
          "underlying": "the drug the patent would produce",
          "contingency": "develop when PV of cash flows exceeds development cost",
          "window_years": 17
        },
        "exclusivity_test": {
          "result": "pass",
          "barrier": "pharmaceutical-patent",
          "scales": {"prerequisite": 1.0, "competitive_advantage": 0.9, "excess_returns": 0.9},
          "exclusivity_factor": 0.85,
          "justification": "blocks similar products, not other treatments for the same disease"
        },
        "pricing_test": {
          "underlying_traded": false,
          "option_traded": true,
          "exercise_cost_knowable": true,
          "conditions_met": 2,
          "engine": "black-scholes",
          "confidence": "medium"
        }
      },
      "verdict": "valued",
      "rejection_reason": null,
      "inputs": {
        "spot": 3422, "spot_basis": "capital budgeting of launch cash flows",
        "strike": 2875, "strike_basis": "PV of development cost",
        "time_to_expiry": 17,
        "volatility": 0.4733, "volatility_source": "biotech industry firm variance 0.224",
        "riskfree_rate": 0.067, "riskfree_basis": "17-year government bond, cost-of-capital.json",
        "dividend_yield": 0.0589, "dividend_yield_basis": "cost of delay 1/17"
      },
      "raw_option_value": 905,
      "exclusivity_factor": 0.85,
      "claimed_option_value": 769,
      "intrinsic_value": 547,
      "optimal_exercise": {"exercise_now": false, "crossover_years_remaining": 8},
      "sensitivity": [
        {"driver": "volatility", "low": 610, "base": 769, "high": 940},
        {"driver": "exclusivity_factor", "low": 452, "base": 769, "high": 905}
      ],
      "double_count": {
        "claim": "Avonex revenue growth",
        "artifact": "forecast.json",
        "driver": "revenue_growth years 3-10",
        "status": "flagged"
      }
    }
  ],
  "totals": {
    "candidates_screened": 4,
    "rejected": 3,
    "valued": 1,
    "decision_tree_only": 0,
    "total_claimed_option_value": 769,
    "share_of_equity_value": 0.18
  },
  "findings": [
    {
      "id": "RO-F1",
      "severity": "high",
      "target_stage": "intrinsic-valuation",
      "claim": "patent growth appears in both the option value and the forecast",
      "suggested_fix": "remove the Avonex ramp from forecast.json revenue growth"
    }
  ],
  "constraints_honored": ["require-exclusivity-scaling", "no-option-premium-without-gate"],
  "confidence": "medium",
  "unresolved": ["reserve quantity is a single geologist estimate with no range"]
}
```

Every candidate appears, including the rejected ones. A rejection carries
`"verdict": "rejected"` and a `rejection_reason` naming the test that failed and why.
`claimed_option_value` is zero for anything not valued.

**`real-options.md`** — readable by someone who will never open the JSON. Lead with the
count: how many candidates were screened, how many were rejected, and why. Give each
rejection a short paragraph, because a rejected premium is a finding in its own right. For
each survivor, state the option in business terms, show the inputs and where each came
from, give the raw and scaled values, and give the range. Close with the double-count items
you flagged and what the composition table shows. Quote no value to the dollar.

## Constraints

Constraint IDs from `classification.json` that apply to this stage:

- `no-option-premium-without-gate` — all three tests run, in order, before any premium is
  admissible. A candidate that fails test 1 or test 2 adds nothing. Not a smaller number:
  nothing.
- `require-exclusivity-scaling` — the exclusivity factor is stated, justified and applied.
  An unstated factor is a claim of full exclusivity, and you do not make that claim by
  silence.
- `single-charge-per-risk` — an option value and the same upside inside DCF growth are one
  claim charged twice. Route each claim to exactly one device.

What you refuse, and what you do instead:

| Refused | Instead |
|---|---|
| Adding a premium for an opportunity any competitor can take | Reject it, and say in the markdown that the absence of a barrier is the reason |
| Using high volatility to rescue a case with no barriers | Reject it. High variance multiplies zero |
| Pricing an option when the underlying facts are absent | Return `needs_input` naming the facts |
| Valuing producing reserves as options | Price developed reserves as a discounted cash flow and say so; only undeveloped reserves are the call |
| Leaving the cost-of-delay yield at zero | Use `1/n` and justify any departure |
| Building a decision tree and adding an option premium | Pick one representation |
| Adding an equity-as-call value to a DCF equity value | Report it as an alternative estimate of the same equity, never additive, and never alongside a failure probability that charges the same risk |
| Editing `forecast.json` or `dcf-result.json` to remove double-counted growth | Raise a finding and return it |
| Quoting a real-option value to the dollar | Two significant figures, with a range |
| Doing arithmetic in prose | The toolkit subcommands, or a recorded `python3 -c` expression |

Two more. Where the pricing test fails and no script covers the decision tree roll-back,
say so in the return rather than rolling it back by hand. And where a candidate is equity
in a distressed firm, confirm the orchestrator routed it here rather than to the
special-situations stage, so the same risk is not priced in two places.

## Return

A structured summary, then a short status line as the last line of the answer. Status is
one of `complete`, `blocked`, `needs_input`, or `not_applicable`.

```
Artifacts:  <abs path>/real-options.json, <abs path>/real-options.md
Valued:     RO1 Avonex patent — raw 905, factor 0.85, claimed 769 (range 450-940)
Rejected:   RO2 expansion into adjacent market (test 2: first-mover only)
            RO3 excess debt capacity (test 2: no excess returns, flexibility worth zero)
            RO4 supplier exit clause (test 1: no counterparty obliged to buy)
Composition: claimed option value is 18% of equity value
Findings:   RO-F1 high — Avonex growth is in forecast.json as well as in the option;
            remove it from the forecast. Owning stage: intrinsic-valuation.
Vintages:   biotech variance table 2024-01; riskfree from cost-of-capital.json
Confidence: medium — volatility is an industry proxy, not a measurement of this drug

real-options-analyst: complete — 4 candidates screened, 3 rejected, 1 valued.
```

For `blocked`, name the missing artifact or field and nothing else. For `needs_input`, give
the candidate, the specific question, and the options the orchestrator should put to the
user. Rejecting every candidate is a complete run, not a failed one. Report it as
`complete` with `total_claimed_option_value` of zero and say plainly that no premium is
admissible.

