# Analysis & validation code -- generic, reads the globals left by the strategy code
# (symbols, start_date, end_date, init_equity, portfolio.st, account.st).

# %% Headline Performance
library(PerformanceAnalytics)
strategy_eq <- getAccount(account.st)$summary$End.Eq
final_eq <- as.numeric(last(strategy_eq))
total_ret_pct <- (final_eq / init_equity - 1) * 100
strategy_ret <- dailyReturn(strategy_eq)
ann_ret_pct <- as.numeric(Return.annualized(strategy_ret)) * 100
sharpe <- as.numeric(SharpeRatio.annualized(strategy_ret, Rf = 0))
maxdd_pct <- as.numeric(maxDrawdown(strategy_ret)) * 100
tstats_kpi <- tradeStats(portfolio.st)
n_trades <- sum(tstats_kpi$Num.Trades)
n_winners <- sum(round(tstats_kpi$Percent.Positive / 100 * tstats_kpi$Num.Trades))
win_rate <- n_winners / n_trades * 100
profit_factor <- sum(tstats_kpi$Gross.Profits) / abs(sum(tstats_kpi$Gross.Losses))
kpi_df <- data.frame(
  Metric = c("Final Equity", "Total Return", "Annualized Return", "Annualized Sharpe",
             "Max Drawdown", "Closed Trades", "Win Rate", "Profit Factor"),
  Value = c(sprintf("$%s", format(round(final_eq), big.mark = ",", scientific = FALSE)),
            sprintf("%.2f%%", total_ret_pct), sprintf("%.2f%%", ann_ret_pct), sprintf("%.2f", sharpe),
            sprintf("%.2f%%", maxdd_pct), sprintf("%d", n_trades), sprintf("%.1f%%", win_rate),
            sprintf("%.2f", profit_factor)))
gt::gt(kpi_df) |>
  gt::tab_header(title = "Headline Results", subtitle = sprintf("%s to %s", start_date, end_date)) |>
  gt::cols_align(align = "right", columns = "Value")

# %% Strategy vs. Benchmarks
#' The strategy against two passive benchmarks over the same window: SPY buy-and-hold and an
#' equal-weight buy-and-hold basket of the traded symbols.
spy <- getSymbols("SPY", from = start_date, to = end_date, auto.assign = FALSE, src = "yahoo", adjust = FALSE)
spy <- adjustOHLC(spy, use.Adjusted = TRUE)
spy_eq <- init_equity * Cl(spy) / as.numeric(Cl(spy)[1])
basket_eq <- Reduce(`+`, lapply(symbols, function(s) {
  px <- Cl(get(s)); (init_equity / length(symbols)) * px / as.numeric(px[1])
}))
comparison <- merge(strategy_eq, spy_eq, basket_eq, join = "inner")
colnames(comparison) <- c("Strategy", "SPY", "Equal-weight basket")
plot(as.zoo(comparison), plot.type = "single", col = c("#00E08F", "#8B5CF6", "#2563EB"),
     lwd = 2, xlab = "", ylab = "Equity ($)", main = "Strategy vs. Benchmark Equity Curves")
legend("topleft", legend = colnames(comparison), col = c("#00E08F", "#8B5CF6", "#2563EB"), lwd = 2, bty = "n")
bench_final <- as.numeric(last(comparison))
gt::gt(data.frame(Series = colnames(comparison),
                  `Final Equity` = sprintf("$%s", format(round(bench_final), big.mark = ",")),
                  `Total Return` = sprintf("%.2f%%", (bench_final / init_equity - 1) * 100), check.names = FALSE))

# %% Drawdown
dd <- Drawdowns(strategy_ret) * 100
plot(as.zoo(dd), col = "#FF4D6A", lwd = 1.5, xlab = "", ylab = "Drawdown (%)", main = "Strategy Drawdown")
abline(h = 0, lty = 2, col = "gray60")

# %% Portfolio Exposure Over Time
#' Mark-to-market share of equity held in positions, from blotter's own position values.
pos_value <- Reduce(`+`, lapply(symbols, function(s) getPortfolio(portfolio.st)$symbols[[s]]$posPL.USD$Pos.Value))
exposure_pct <- 100 * merge(pos_value, strategy_eq, join = "inner")[, 1] / merge(pos_value, strategy_eq, join = "inner")[, 2]
plot(as.zoo(exposure_pct), col = "#2563EB", lwd = 1.5, xlab = "", ylab = "% of Equity Invested", main = "Portfolio Exposure Over Time")

# %% Rolling 3-Month Sharpe
roll <- rollapply(strategy_ret, width = 63, FUN = function(r) mean(r) / sd(r) * sqrt(252), align = "right", fill = NA)
plot(as.zoo(roll), col = "#8B5CF6", lwd = 1.5, xlab = "", ylab = "Annualized Sharpe", main = "Rolling 3-Month Sharpe")
abline(h = 0, lty = 2, col = "gray60")
