---
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
