Volatility

Key Concepts

Introduction

Volatility models measure the historical volatility of an asset. They are mostly used to calculate the volatility of the underlying security of an Option because the implied volatility of an Option contract needs an initial guess. The historical volatility doesn't need to be the standard deviation of the asset prices. The various volatility models in LEAN each have a unique methodology to calculate volatility.

LEAN also provides an indicator implementation of implied volatility. It provides higher flexibility on Option price model selection, volatility modeling, and allows IV smoothing through call-put pair. For details, see Implied Volatility.

Set Models

To set the volatility model of the underlying security of an Option, set the VolatilityModel property of the Security object. The volatility model can have a different resolution than the underlying asset subscription.

public override void Initialize()
{
    var underlyingSecurity = AddEquity("SPY");
    // Usually, the historical and implied volatility is calculated using 30-day standard deviation of return
    underlyingSecurity.VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(30);
}
def initialize(self) -> None:
    underlying_security = self.add_equity("SPY")
    # Usually, the historical and implied volatility is calculated using 30-day standard deviation of return
    underlying_security.volatility_model = StandardDeviationOfReturnsVolatilityModel(30)

You can also set the volatility model in a security initializer. If your algorithm has a universe of underlying assets, use the security initializer technique. In order to initialize single security subscriptions with the security initializer, call SetSecurityInitializerset_security_initializer before you create the subscriptions.

public class BrokerageModelExampleAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        // In the Initialize method, set the security initializer to seed initial the prices and models of assets.
        SetSecurityInitializer(new MySecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices)));
    }
}

public class MySecurityInitializer : BrokerageModelSecurityInitializer
{
    public MySecurityInitializer(IBrokerageModel brokerageModel, ISecuritySeeder securitySeeder)
        : base(brokerageModel, securitySeeder) {}    
    public override void Initialize(Security security)
    {
        // First, call the superclass definition.
        // This method sets the reality models of each security using the default reality models of the brokerage model.
        base.Initialize(security);

        // Next, overwrite the volatility model
        if (security.Type == SecurityType.Equity)
        {
            security.VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(30);
        }    }
}
class BrokerageModelExampleAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        # In the Initialize method, set the security initializer to seed initial the prices and models of assets.
        self.set_security_initializer(MySecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))

# Outside of the algorithm class
class MySecurityInitializer(BrokerageModelSecurityInitializer):

    def __init__(self, brokerage_model: IBrokerageModel, security_seeder: ISecuritySeeder) -> None:
        super().__init__(brokerage_model, security_seeder)    
    def initialize(self, security: Security) -> None:
        # First, call the superclass definition.
        # This method sets the reality models of each security using the default reality models of the brokerage model.
        super().initialize(security)

        # Next, overwrite the volatility model
        if security.Type == SecurityType.EQUITY:
            security.volatility_model = StandardDeviationOfReturnsVolatilityModel(30)

To view all the pre-built volatility models, see Supported Models.

Default Behavior

The default underlying volatility model for Equity Options and Index Options is the StandardDeviationOfReturnsVolatilityModel based on 30 days of daily resolution data. The default underlying volatility model for Future Options is the NullVolatilityModel.

Model Structure

Volatility models should extend the BaseVolatilityModel class. Extensions of the BaseVolatilityModel class must have Updateupdate and GetHistoryRequirementsget_history_requirements methods and a Volatilityvolatility property. The Updateupdate method receives Security and BaseData objects and then updates the Volatilityvolatility. The GetHistoryRequirementsget_history_requirements method receives Security and DateTimedatetime objects and then returns a list of HistoryRequest objects that represent the history requests to warm up the model. Volatility models receive data at each time step in the algorithm to update their state.

public class CustomVolatilityModelExampleAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        var underlyingSecurity = AddIndex("VIX");
        // Apply custom volatility model for specific underlyings, often they are synthetic products or with different market calendar
        underlyingSecurity.VolatilityModel = new MyVolatilityModel();
    }
}

// Define the custom volatility model outside of the algorithm
public class MyVolatilityModel : BaseVolatilityModel
{
    public override decimal Volatility { get; }

    public override void SetSubscriptionDataConfigProvider(
        ISubscriptionDataConfigProvider subscriptionDataConfigProvider)
    {
        SubscriptionDataConfigProvider = subscriptionDataConfigProvider;
    }

    public override void Update(Security security, BaseData data)
    {
    }

    public override IEnumerable<HistoryRequest> GetHistoryRequirements(
        Security security,
        DateTime utcTime)
    {
        return base.GetHistoryRequirements(security, utcTime);
    }

    public new IEnumerable<HistoryRequest> GetHistoryRequirements(
        Security security, 
        DateTime utcTime,
        Resolution? resolution,
        int barCount)
    {
        return base.GetHistoryRequirements(security, utcTime, resolution, barCount);
    }
}
class CustomVolatilityModelExampleAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        underlying_security = self.add_index("VIX")
        # Apply custom volatility model for specific underlyings, often they are synthetic products or with different market calendar
        underlying_security.volatility_model = MyVolatilityModel()

# Define the custom volatility model outside of the algorithm
class MyVolatilityModel(BaseVolatilityModel):
    volatility: float = 0

    def set_subscription_data_config_provider(self,
        subscription_data_config_provider: ISubscriptionDataConfigProvider) -> None:
        super().set_subscription_data_config_provider(subscription_data_config_provider)

    def update(self, security: Security, data: BaseData) -> None:
        pass

    def get_history_requirements(self,
         security: Security,
         utc_time: datetime,
         resolution: resolution = None,
         bar_count: int = None) -> List[HistoryRequest]:
        return super().get_history_requirements(security, utc_time, resolution, bar_count)

For a full example algorithm, see this backtestthis backtest.

Warm Up Models

To use your volatility model as the inital guess for the implied volatility, warm up the volatility model of the underlying security. If you subscribe to all the Options in the Initializeinitialize method, set a warm-up period to warm up their volatility models. The warm-up period should provide the volatility models with enough data to compute their values.

public override void Initialize()
{
    // For default 30-day SD of return as volatility, you need 30+1 trading day to warm up
    SetWarmUp(31, Resolution.Daily);
}

public override void OnData(Slice slice)
{
    // Only take the warmed-up volatility value into account for accuracy issue
    if (IsWarmingUp) return;
}
def initialize(self) -> None:
    # For default 30-day SD of return as volatility, you need 30+1 trading day to warm up
    self.set_warm_up(30, Resolution.DAILY)

def on_data(self, slice: Slice) -> None:
    # Only take the warmed-up volatility value into account for accuracy issue
    if self.is_warming_up:
        return

If you have a dynamic universe of underlying assets and add Option contracts to your algorithm with the AddOptionContractadd_option_contract, AddIndexOptionContractadd_index_option_contract, or AddFutureOptionContractadd_future_option_contract methods, warm up the volatility model when the underlying asset enters your universe. We recommend you do this inside a security initializer.

public class BrokerageModelExampleAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        // In the Initialize method, set the security initializer to seed initial the prices and models of assets.
        SetSecurityInitializer(new MySecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices), this));
    }
}

public class MySecurityInitializer : BrokerageModelSecurityInitializer
{
    private QCAlgorithm _algorithm;

    public MySecurityInitializer(IBrokerageModel brokerageModel, ISecuritySeeder securitySeeder, QCAlgorithm algorithm)
        : base(brokerageModel, securitySeeder) 
    {
        _algorithm = algorithm;
    }    
    public override void Initialize(Security security)
    {
        // First, call the superclass definition.
        // This method sets the reality models of each security using the default reality models of the brokerage model.
        base.Initialize(security);

        // Next, overwrite and warm up the volatility model
        if (security.Type == SecurityType.Equity) // Underlying asset type
        {
            security.VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(30);
            foreach (var tradeBar in _algorithm.History(security.Symbol, 30, Resolution.Daily))
            {
                security.VolatilityModel.Update(security, tradeBar);
            }
        }    }
}
class BrokerageModelExampleAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        # In the Initialize method, set the security initializer to seed initial the prices and models of assets.
        self.set_security_initializer(MySecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices), self))

# Outside of the algorithm class
class MySecurityInitializer(BrokerageModelSecurityInitializer):

    def __init__(self, brokerage_model: IBrokerageModel, security_seeder: ISecuritySeeder, algorithm: QCAlgorithm) -> None:
        super().__init__(brokerage_model, security_seeder)
        self._algorithm = algorithm    
    def initialize(self, security: Security) -> None:
        # First, call the superclass definition.
        # This method sets the reality models of each security using the default reality models of the brokerage model.
        super().initialize(security)

        # Next, overwrite and warm up the volatility model
        if security.type == SecurityType.EQUITY:  # Underlying asset type
            security.volatility_model = StandardDeviationOfReturnsVolatilityModel(30)
            trade_bars = self._algorithm.history[TradeBar](security.symbol, 30, Resolution.DAILY)
            for trade_bar in trade_bars:
                security.volatility_model.update(security, trade_bar)

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: