--- title: "Creating Custom Models" author: "Simon Mueller" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Creating Custom Models} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` # Introduction The EventStudy package uses an object-oriented model system based on R6 classes. Every model inherits from `ModelBase`, which defines the interface that the pipeline expects. This vignette shows how to create your own custom model and plug it into the event study workflow. # The ModelBase Interface All models must implement two methods: 1. **`fit(data_tbl)`** -- Estimate model parameters from the estimation window. 2. **`abnormal_returns(data_tbl)`** -- Compute abnormal returns for all observations (estimation + event windows). The `fit()` method should also populate `private$.statistics` with at least `sigma` and `degree_of_freedom`, which are used by the test statistics. # Example: Industry-Adjusted Model Suppose you want an industry-adjusted model where the expected return equals the industry index return rather than the broad market index. ```{r, eval=FALSE} library(EventStudy) IndustryAdjustedModel <- R6::R6Class("IndustryAdjustedModel", inherit = ModelBase, public = list( model_name = "IndustryAdjustedModel", fit = function(data_tbl) { # No estimation needed -- just compute statistics private$.is_fitted <- TRUE private$calculate_statistics(data_tbl) }, abnormal_returns = function(data_tbl) { # AR = firm return - industry index return # Assumes data_tbl has an 'industry_return' column data_tbl %>% dplyr::mutate(abnormal_returns = firm_returns - industry_return) } ), private = list( calculate_statistics = function(data_tbl) { estimation_tbl <- data_tbl %>% dplyr::filter(estimation_window == 1) residuals <- estimation_tbl$firm_returns - estimation_tbl$industry_return private$add_residuals(residuals) private$first_order_autocorrelation(residuals) sigma <- sd(residuals, na.rm = TRUE) private$.statistics$sigma <- sigma private$.statistics$degree_of_freedom <- length(residuals) - 1 # Forecast error correction event_window_tbl <- data_tbl %>% dplyr::filter(event_window == 1) private$calculate_forecast_error_correction( sigma, nrow(estimation_tbl), estimation_tbl$index_returns, event_window_tbl$index_returns ) } ) ) ``` # Using the Custom Model Once defined, use your model exactly like any built-in model: ```{r, eval=FALSE} # Create parameter set with custom model params <- ParameterSet$new( return_model = IndustryAdjustedModel$new() ) # Run the event study task <- EventStudyTask$new(firm_data, index_data, request_data) task <- run_event_study(task, params) ``` # Example: Excess Return Model A model that simply computes excess returns over the risk-free rate: ```{r, eval=FALSE} ExcessReturnModel <- R6::R6Class("ExcessReturnModel", inherit = ModelBase, public = list( model_name = "ExcessReturnModel", risk_free_rate = 0.0001, # daily risk-free rate initialize = function(risk_free_rate = 0.0001) { self$risk_free_rate <- risk_free_rate }, fit = function(data_tbl) { private$.is_fitted <- TRUE estimation_tbl <- data_tbl %>% dplyr::filter(estimation_window == 1) residuals <- estimation_tbl$firm_returns - self$risk_free_rate private$.statistics$sigma <- sd(residuals, na.rm = TRUE) private$.statistics$degree_of_freedom <- length(residuals) - 1 private$add_residuals(residuals) private$first_order_autocorrelation(residuals) }, abnormal_returns = function(data_tbl) { data_tbl %>% dplyr::mutate(abnormal_returns = firm_returns - self$risk_free_rate) } ) ) ``` # Using Factor Models The package includes several built-in factor models that inherit from `LinearFactorModel`: - `FamaFrench3FactorModel` -- SMB, HML, market excess - `FamaFrench5FactorModel` -- adds RMW, CMA - `Carhart4FactorModel` -- 3-factor + momentum These require factor data to be provided via the `factor_tbl` argument in `EventStudyTask`: ```{r, eval=FALSE} # Factor data with date column matching firm/index data factor_data <- tibble::tibble( date = dates, smb = smb_returns, hml = hml_returns, risk_free_rate = rf_rate ) task <- EventStudyTask$new(firm_data, index_data, request_data, factor_tbl = factor_data) params <- ParameterSet$new( return_model = FamaFrench3FactorModel$new() ) task <- run_event_study(task, params) ``` # Key Points - Always inherit from `ModelBase` - Implement `fit()` and `abnormal_returns()` - Populate `private$.statistics$sigma` and `private$.statistics$degree_of_freedom` - Use `private$add_residuals()` and `private$first_order_autocorrelation()` for diagnostics compatibility - Set `private$.is_fitted <- TRUE` after successful fitting - The model is deep-cloned for each event, so you can store event-specific state