---
title: "Getting Started with PortfolioTesteR"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting Started with PortfolioTesteR}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 5,
  message = FALSE,
  warning = FALSE
)
```

# PortfolioTesteR: Test Investment Strategies with English-Like Code

PortfolioTesteR makes quantitative investing accessible through intuitive, English-like syntax. This vignette walks through five strategies from beginner to advanced, all using the included sample data so it builds quickly and reliably.

> **Note:** Some functions also support external data (e.g., `yahoo_adapter()`), but for CRAN-friendly vignettes we use the bundled datasets.

## Installation

```{r install, eval=FALSE}
# Install from GitHub
# (Skip during CRAN checks and vignette builds.)
devtools::install_github("alb3rtazzo/PortfolioTesteR")
```

## Load the Library

```{r load}
library(PortfolioTesteR)
```

## Strategy 1: Simple Momentum (Beginner)

Buy the stocks with the highest 12-week returns, weight them equally, and backtest.

```{r strategy1}
# Load included weekly prices
data(sample_prices_weekly)

# 1) Momentum signal
momentum <- calc_momentum(sample_prices_weekly, lookback = 12)

# 2) Select top 10 by momentum
selected <- filter_top_n(momentum, n = 10)

# 3) Equal weights
weights <- weight_equally(selected)

# 4) Backtest
result1 <- run_backtest(
  prices = sample_prices_weekly,
  weights = weights,
  initial_capital = 100000,
  name = "Simple Momentum"
)

# 5) Results
print(result1)
summary(result1)
```

```{r strategy1_plot}
plot(result1, type = "performance")
```

## Strategy 2: Multi-Signal Combination (Intermediate)

Combine **momentum** (good = high) and **stability** (good = low volatility). Select stocks that rank well on both, then combine weights.

```{r strategy2}
# Need daily data for volatility
data(sample_prices_daily)

# A) Momentum (12-week)
momentum <- calc_momentum(sample_prices_weekly, lookback = 12)

# B) Daily volatility -> align weekly -> invert (low vol = high score)
daily_vol <- calc_rolling_volatility(sample_prices_daily, lookback = 20)
weekly_vol <- align_to_timeframe(
  high_freq_data = daily_vol,
  low_freq_dates = sample_prices_weekly$Date,
  method = "forward_fill"
)
stability_signal <- invert_signal(weekly_vol)

# Select top 20 for each signal
m_sel <- filter_top_n(momentum, n = 20)
s_sel <- filter_top_n(stability_signal, n = 20)

# AND-combine the selections
both <- combine_filters(m_sel, s_sel, op = "and")

# Weight each way then blend 60/40
w_mom <- weight_by_signal(both, momentum)
w_stab <- weight_by_signal(both, stability_signal)
weights2 <- combine_weights(list(w_mom, w_stab), weights = c(0.6, 0.4))

# Backtest
result2 <- run_backtest(
  prices = sample_prices_weekly,
  weights = weights2,
  initial_capital = 100000,
  name = "Momentum + Low Vol"
)

print(result2)
summary(result2)
```

```{r strategy2_plot}
plot(result2, type = "performance")
```

## Strategy 3: Risk-Managed Momentum with Stop Loss (Intermediate)

Add a **15% stop loss** monitored on daily prices. (We compare the same strategy with and without stops.)

```{r strategy3}
# Signals and selection
momentum <- calc_momentum(sample_prices_weekly, lookback = 12)
sel <- filter_top_n(momentum, n = 10)
weights_mom <- weight_by_signal(sel, momentum)

# With 15% stop-loss (daily monitoring)
result3_with <- run_backtest(
  prices = sample_prices_weekly,
  weights = weights_mom,
  initial_capital = 100000,
  name = "Momentum with 15% Stop Loss",
  stop_loss = 0.15,
  stop_monitoring_prices = sample_prices_daily
)

# Without stop-loss
result3_no <- run_backtest(
  prices = sample_prices_weekly,
  weights = weights_mom,
  initial_capital = 100000,
  name = "Momentum without Stop Loss"
)

cat("WITH Stop Loss:\n")
print(result3_with)
cat("\nWITHOUT Stop Loss:\n")
print(result3_no)
```

```{r strategy3_plot}
# Plot both separately to avoid cramped figures
plot(result3_with, type = "performance")
plot(result3_no, type = "performance")
```

## Strategy 4: Regime-Adaptive Volatility (Advanced)

Detect market **volatility regimes** using SPY's rolling volatility. Use **defensive** weights in high-vol regimes and **aggressive** weights in low-vol regimes.

```{r strategy4}
# Extract SPY for regime detection
spy_prices <- sample_prices_weekly[, .(Date, SPY)]

# Trading universe (exclude SPY)
trading_symbols <- setdiff(names(sample_prices_weekly), c("Date", "SPY"))
trading_prices <- sample_prices_weekly[, c("Date", trading_symbols), with = FALSE]
trading_daily  <- sample_prices_daily[,  c("Date", trading_symbols), with = FALSE]

# SPY weekly returns & 20-week rolling volatility (annualized)
spy_returns <- c(NA, diff(spy_prices$SPY) / head(spy_prices$SPY, -1))
spy_vol <- zoo::rollapply(spy_returns, width = 20, FUN = sd, fill = NA, align = "right") * sqrt(52)

# High-vol regime = above median
vol_threshold <- median(spy_vol, na.rm = TRUE)
high_vol <- spy_vol > vol_threshold

# Selection by momentum
mom <- calc_momentum(trading_prices, lookback = 12)
sel <- filter_top_n(mom, n = 15)

# Defensive (prefer low vol) vs Aggressive (prefer high vol) weights
w_def <- weight_by_volatility(
  selected_df = sel,
  vol_timeframe_data = trading_daily,
  strategy_timeframe_data = trading_prices,
  lookback_periods = 20,
  low_vol_preference = TRUE,
  vol_method = "std"
)

w_agg <- weight_by_volatility(
  selected_df = sel,
  vol_timeframe_data = trading_daily,
  strategy_timeframe_data = trading_prices,
  lookback_periods = 20,
  low_vol_preference = FALSE,
  vol_method = "std"
)

# Switch weights by regime (defensive when high-vol is TRUE)
weights4 <- switch_weights(
  weights_a = w_agg,  # used when condition is FALSE (low vol)
  weights_b = w_def,  # used when condition is TRUE  (high vol)
  use_b_condition = high_vol
)

result4 <- run_backtest(
  prices = trading_prices,
  weights = weights4,
  initial_capital = 100000,
  name = "Regime-Adaptive Strategy"
)

print(result4)
summary(result4)
```

```{r strategy4_plot}
plot(result4, type = "performance")
```

## Strategy 5: Multi-Factor with Position Limits (Advanced)

Combine **momentum** and **stability** signals, then enforce a **max positions** limit to control concentration. Weight 70% by momentum strength and 30% by stability.

```{r strategy5}
# Signals
momentum <- calc_momentum(sample_prices_weekly, lookback = 12)
daily_vol <- calc_rolling_volatility(sample_prices_daily, lookback = 20)
weekly_vol <- align_to_timeframe(daily_vol, sample_prices_weekly$Date, method = "forward_fill")
stability <- invert_signal(weekly_vol)

# Selection & position cap
top30 <- filter_top_n(momentum, n = 30)
sel15 <- limit_positions(top30, momentum, max_positions = 15)

# Weights and blend (70/30)
w_m <- weight_by_signal(sel15, momentum)
w_s <- weight_by_signal(sel15, stability)
weights5 <- combine_weights(list(w_m, w_s), weights = c(0.7, 0.3))

# Backtest
result5 <- run_backtest(
  prices = sample_prices_weekly,
  weights = weights5,
  initial_capital = 100000,
  name = "Multi-Factor with Position Limits"
)

print(result5)
summary(result5)
```

```{r strategy5_plot}
plot(result5, type = "performance")
```

## Strategy 6: StochRSI Acceleration + Inverse-Vol Risk Parity


Advanced Strategy: StochRSI Acceleration + Inverse-Vol Risk Parity
- Gate to high StochRSI (>= 0.80), then select top-12 by acceleration
- Allocate by inverse-volatility risk parity using DAILY prices
- Backtest on the weekly grid (bundled datasets only; CRAN-friendly)
```{r strategy6}
# Data
data(sample_prices_weekly)
data(sample_prices_daily)

# Exclude broad ETFs from stock-selection universe
symbols_all   <- setdiff(names(sample_prices_weekly), "Date")
stock_symbols <- setdiff(symbols_all, c("SPY", "TLT"))

weekly_stocks <- sample_prices_weekly[, c("Date", stock_symbols), with = FALSE]
daily_stocks  <- sample_prices_daily[,  c("Date", stock_symbols), with = FALSE]

# StochRSI "acceleration" signal (weekly)
stochrsi    <- calc_stochrsi(weekly_stocks, length = 14)   # in [0,1]
stochrsi_ma <- calc_moving_average(stochrsi, window = 5)
accel       <- calc_distance(stochrsi, stochrsi_ma)        # positive = rising

# Gate to high StochRSI zone, then take top-12 by acceleration
high_zone <- filter_above(stochrsi, value = 0.80)
sel <- filter_top_n_where(
  signal_df     = accel,
  n             = 12,
  condition_df  = high_zone,
  min_qualified = 8,
  ascending     = FALSE
)

# Allocation: inverse-volatility risk parity (DAILY prices)
w_ivol <- weight_by_risk_parity(
  selected_df      = sel,
  prices_df        = daily_stocks,
  method           = "inverse_vol",
  lookback_periods = 126,  # ~6 months
  min_periods      = 60
)

# Backtest on the weekly grid
res_stochrsi <- run_backtest(
  prices          = weekly_stocks,
  weights         = w_ivol,
  initial_capital = 100000,
  name            = "StochRSI Accel + InvVol RP"
)

print(res_stochrsi)
summary(res_stochrsi)


```


```{r strategy6_plot}
plot(res_stochrsi, type = "performance")
```



## Optional: Live Data via Yahoo (CRAN-safe)

Below is a minimal example that fetches prices from Yahoo Finance and runs the same
"calculate -> filter -> weight -> backtest" pipeline. The code is **disabled** inside
CRAN/devtools checks. To run it locally, set `Sys.setenv(RUN_LIVE = "true")` before knitting.

```{r live_data, eval = identical(Sys.getenv("RUN_LIVE"), "true")}
library(PortfolioTesteR)

# Fetch weekly data for a small set of tickers
tickers <- c("AAPL","MSFT","AMZN","GOOGL","META")
px_weekly <- yahoo_adapter(
  symbols   = tickers,
  frequency = "weekly"
)

# Simple momentum: top-3 by 12-week return, equal weight
mom  <- calc_momentum(px_weekly, lookback = 12)
sel  <- filter_top_n(mom, n = 3)
w_eq <- weight_equally(sel)

res_yh <- run_backtest(
  prices          = px_weekly,
  weights         = w_eq,
  initial_capital = 100000,
  name            = "Yahoo: Simple Momentum (Top 3)"
)

print(res_yh)
summary(res_yh)


```

## Key Concepts Recap

**The PortfolioTesteR Pattern**

1. **Calculate** signals (momentum, volatility, etc.)
2. **Filter** the universe (top-N, thresholds, logical combinations)
3. **Weight** the portfolio (equally, by signal, by volatility, etc.)
4. **Backtest** the strategy
5. **Analyze** with built-in metrics and visualizations

**Function Families**

- **Indicators**: `calc_momentum()`, `calc_rsi()`, `calc_rolling_volatility()`, `calc_moving_average()`
- **Selection**: `filter_top_n()`, `filter_above()`, `filter_between()`, `combine_filters()`
- **Weighting**: `weight_equally()`, `weight_by_signal()`, `weight_by_rank()`, `weight_by_volatility()`, `combine_weights()`
- **Execution**: `run_backtest()`
- **Utilities**: `align_to_timeframe()`, `invert_signal()`, `limit_positions()`, `switch_weights()`
- **Analysis**: `analyze_performance()`, `summary()`, `plot()`

## Getting Help

```{r help, eval=FALSE}
?run_backtest
?calc_momentum
?filter_top_n
?analyze_performance
```

## Citation

If you use PortfolioTesteR in your research, please cite:

> Pallotta, A. (2025). *PortfolioTesteR: Test Investment Strategies with English-Like Code.* R package version 0.1.2. https://github.com/AlbertoPallotta/PortfolioTesteR

