investfly.models.marketdata.OptionPricer

Black-Scholes option pricer used by Investfly automated options strategies.

Why this exists in the SDK:

  • Strategy code (live + backtest) needs a deterministic synthetic option premium when no real option chain quote is available. Real chains are still used when present; this is the fallback.
  • Backtest historically replays underlying OHLCV but does not yet replay full option chains. We use Black-Scholes with computed/historical IV proxies to settle option premia in backtest in a deterministic, reproducible way.
  • Decision: use realized-volatility from underlying daily returns as the default IV proxy when no historical IV column is available. Annualize from daily standard deviation by sqrt(252).
  • Risk-free rate is a static default (0.04) for MVP; can be replaced by config later.
  • No dividend yield handling for MVP (treat as 0); expand later for option-on-stock with non-zero dividend yield.

This pricer intentionally has no I/O dependencies so it can be reused inside the SDK, the Python runtime, the backtest engine, and unit tests.

DEFAULT_RISK_FREE_RATE: float = 0.04
TRADING_DAYS_PER_YEAR: int = 252
MIN_IMPLIED_VOL: float = 0.05
DEFAULT_IMPLIED_VOL_FALLBACK: float = 0.3
@dataclass(frozen=True)
class GreeksResult:

Black-Scholes Greeks bundle returned alongside the theoretical premium.

GreeksResult(delta: float, gamma: float, theta: float, vega: float, rho: float)
delta: float
gamma: float
theta: float
vega: float
rho: float
@dataclass(frozen=True)
class PricingResult:

Theoretical price + Greeks for a single contract.

PricingResult( price: float, greeks: GreeksResult, impliedVolUsed: float)
price: float
greeks: GreeksResult
impliedVolUsed: float
class OptionPricer:

Black-Scholes pricer for European-style stock/ETF options.

@staticmethod
def realizedVolatilityFromCloses(closes: Iterable[float]) -> float:

Annualized realized volatility of daily log returns.

Falls back to MIN_IMPLIED_VOL when the input is too short or has zero variance. Inputs are oldest-to-newest closing prices (matching Bar order convention).

@staticmethod
def yearsUntil(expiry: datetime.date, asOf: Optional[datetime.datetime] = None) -> float:

Time-to-expiry in calendar years, with a minimum of 1/365 to keep BS well-defined.

@staticmethod
def price( spot: float, strike: float, yearsToExpiry: float, impliedVol: float, optionType: investfly.models.marketdata.OptionType, riskFreeRate: float = 0.04) -> PricingResult:

Black-Scholes price + Greeks for a European option.

Greeks scaling:

  • delta in [-1, 1] per share (multiply by 100 for per-contract risk).
  • theta is per-year; divide by 365 for per-day theta.
  • vega is per 1.00 of vol (multiply by 0.01 for per-1%-vol).
  • rho is per 1.00 of rate (multiply by 0.01 for per-1%-rate). These conventions match common quant texts; UI/runtime can rescale as needed.
@staticmethod
def deltaToStrike( spot: float, yearsToExpiry: float, impliedVol: float, optionType: investfly.models.marketdata.OptionType, targetDelta: float, riskFreeRate: float = 0.04, strikeStep: float = 1.0) -> float:

Find the strike whose Black-Scholes delta is closest to the target.

Search is a coarse grid scan with a refinement step. Strike grid is anchored at the nearest multiple of strikeStep around spot and bounded at +-3 sigma * sqrt(T) from spot, which is wide enough for typical 30-60 DTE selections and avoids unstable far-OTM delta extrapolation.