knitr::opts_chunk$set(warning=FALSE, echo=FALSE, message=FALSE) library(tidyr) library(jsonlite) library(kableExtra) library(purrr) library(data.table) library(ggplot2) library(PerformanceAnalytics) library(DT) library(roll)
# read json file with results test <- 'C:/Users/Mislav/Documents/GitHub/lean_test/ExuberLocal/optimizations/2021-04-22_09-26-38/31ba6169-b31a-4da8-a92d-d6df29eea480' results_json_path <- file.path(test, paste0(gsub(".*/", "", test), ".json")) # TEST results_json_path <- file.path(params$lean_path, paste0(gsub(".*/", "", params$lean_path), ".json")) result <- read_json(results_json_path) names(result)
Strategija koristi poznati statistički test za identificiranje balona (eng. bubble) na finanicijskim tržištima prema radovima Phillips et al. (2015) i Pavlidis et al. (2016). Radi se o testovima eksplozivnosti, koji balone identificiraju pomoću rekurzivnog ADF testa (augmented Dickey Fuller test). ADF test je uobičajeni statistički test za testiranje (ne)stacionarnosti vremenske serije.
Jednostavno rečeno, kada dinamika cijene postane eksplozivna (nagli rast; koeficijent uz pomaknutu varijablu veći od 1.2; eksplozivan rast), identificiran je balon na tržištu. Exuber test (ovako ćemo dalje nazivati test eksplozivnosti spomenutih autora) se temelji na sadf statistici koja poprima određenu vrijednost. Što je veća sadf vrijednost, veća je vjerojatnost pojave balona. Kada sadf vrijednost prijeđe određenu razinu identificiran je bubble na tržištu. "Određena" razina u izvornim radovima utvrđena je na temelju intervala pouzdanosti, odnosno riječ je o kritičnoj granici (kritična vrijednost se skraćeno se naziva gsadf).
Informacija o pojavi balona može se iskoristiti na dva načina:
Strategija koju ćemo opisati koristi pristup 2, ali dodajemo još jedan kriterij. Osim kriterija po kojem prodajemo imovinu (SPY) kada sadf statistika prijeđe kritičnu vrijednost (npr. vrijednost 1), nužno je da je u tekućem razdoblju ostvaren pad prinosa.
Algoritam se sastoji od sljedećih koraka
Istraživački dio strategije je rađen u R-u, dok je konačni backtest kao i uvijek proveden unutar Lean/Quantconnect okruženja. Napravljena je optimizacija strategije na temelju tri parametra: duljina prozora za koji se računa sadf vrijednost, broj pomaknutih vrijednosti, vrijednost granice.
Rezultati najbolje strategije prikazani su u nižem pdfu (izvještaj je generiran kroz Lean sustav). Optimalni parametri koji su pronađeni na temelju optimizacije u Leanu su 1) kritična vrijednost 1.2 b) duljina prozora 100 sati c) logaritam cijene FALSE 4) izbor varijable sadf:
Izvještaj možete preuzeti klikom na link:
xfun::embed_file('C:/Users/Mislav/Documents/GitHub/lean_test/exuber_result_best.html')
# statistics algo_stats <- as.data.frame(list(result$Statistics)) %>% pivot_longer(., cols = everything()) rows <- seq_len(nrow(algo_stats) %/% 2) kable(list(algo_stats[rows,1:2], algo_stats[-rows, 1:2]), col.names = NULL) %>% kable_styling(full_width = FALSE) %>% kable_material(c("striped", "hover"))
# Equity curve plot benchmark <- rbindlist(result$Charts$Benchmark$Series$Benchmark$Values) benchmark <- benchmark[y > 0] benchmark[, Date := as.POSIXct(x, origin="1970-01-01")] benchmark[, prices := y] benchmark[, Benchmrk := (prices / shift(prices)) - 1] benchmark[, `:=`(x = NULL, y = NULL, prices = NULL)] strategy <- rbindlist(result$Charts$`Strategy Equity`$Series$`Daily Performance`$Values) strategy[, Date := as.POSIXct(x, origin="1970-01-01")] strategy[, Strategy := y / 100] strategy[, `:=`(x = NULL, y = NULL)] equity_curve <- strategy[benchmark, on = 'Date'] equity_curve <- na.omit(equity_curve) charts.PerformanceSummary(equity_curve, plot.engine = 'plotly')
returns <- copy(equity_curve) returns[, `:=`(StrategyCumulative = cumprod(1 + Strategy), BenchmarkCumulative = cumprod(1 + Benchmrk))] datatable(returns, rownames = FALSE, escape = FALSE, extensions = 'Buttons', options = list(dom = 'Blfrtip', buttons = c('copy', 'csv', 'excel', 'pdf', 'print'), lengthMenu = list(c(10,25,50,-1), c(10,25,50,"All"))))
annual_returns <- apply.yearly(as.xts.data.table(returns)[, "Strategy"], Return.cumulative) annual_returns <- as.data.table(annual_returns) annual_returns[, index := data.table::year(index)] mean_return <- mean(annual_returns$Strategy, na.rm = TRUE) datatable(annual_returns, rownames = FALSE, escape = FALSE, extensions = 'Buttons', options = list(dom = 'Blfrtip', buttons = c('copy', 'csv', 'excel', 'pdf', 'print'), lengthMenu = list(c(10,25,50,-1), c(10,25,50,"All"))))
ggplot(annual_returns, aes(x = index, y = Strategy)) + geom_bar(stat='identity', fill = "blue") + geom_hline(yintercept = mean_return, color = "red", linetype = "dashed") + coord_flip()
returns_per_trade <- rbindlist(result$TotalPerformance$ClosedTrades) returns_per_trade <- unique(returns_per_trade[, 2:ncol(returns_per_trade)]) returns_per_trade[, trade_returns := round((ExitPrice / EntryPrice - 1) * 100, 0) / 100] datatable(returns_per_trade, rownames = FALSE, escape = FALSE, extensions = 'Buttons', options = list(dom = 'Blfrtip', buttons = c('copy', 'csv', 'excel', 'pdf', 'print'), lengthMenu = list(c(10,25,50,-1), c(10,25,50,"All"))))
ggplot(returns_per_trade[, .(trade_returns)], aes(x = trade_returns)) + geom_bar(fill = "blue") + scale_x_continuous(labels = scales::percent) + geom_vline(xintercept = mean(returns_per_trade$trade_returns), color = "red", linetype = "dashed")
drawdown <- Drawdowns(as.xts.data.table(strategy)) drawdown <- as.data.table(drawdown) setnames(drawdown, "Strategy", "Drawdown") datatable(drawdown, rownames = FALSE, escape = FALSE, extensions = 'Buttons', options = list(dom = 'Blfrtip', buttons = c('copy', 'csv', 'excel', 'pdf', 'print'), lengthMenu = list(c(10,25,50,-1), c(10,25,50,"All"))))
ggplot(drawdown, aes(x = index, y = Drawdown)) + geom_line() + scale_y_continuous(labels = scales::percent) + geom_area()
ggplot(returns[, .(Date, Strategy)], aes(x = Date, y = Strategy)) + geom_bar(stat="identity")
runCumRets <- function(R, n = 252, annualized = FALSE, scale = NA) { R <- na.omit(R) if (is.na(scale)) { freq = periodicity(R) switch(freq$scale, minute = { stop("Data periodicity too high") }, hourly = { stop("Data periodicity too high") }, daily = { scale = 252 }, weekly = { scale = 52 }, monthly = { scale = 12 }, quarterly = { scale = 4 }, yearly = { scale = 1 }) } cumRets <- cumprod(1+R) if(annualized) { rollingCumRets <- (cumRets/lag(cumRets, k = n))^(scale/n) - 1 } else { rollingCumRets <- cumRets/lag(cumRets, k = n) - 1 } return(rollingCumRets) } runSharpe <- function(R, n = 252, scale = NA, volFactor = 1) { if (is.na(scale)) { freq = periodicity(R) switch(freq$scale, minute = { stop("Data periodicity too high") }, hourly = { stop("Data periodicity too high") }, daily = { scale = 252 }, weekly = { scale = 52 }, monthly = { scale = 12 }, quarterly = { scale = 4 }, yearly = { scale = 1 }) } rollingAnnRets <- runCumRets(R, n = n, annualized = TRUE) rollingAnnSD <- sapply(R, TTR::runSD, n = n)*sqrt(scale) rollingSharpe <- rollingAnnRets/rollingAnnSD ^ volFactor return(rollingSharpe) } plotRunSharpe <- function(R, n = 252, ...) { sharpes <- runSharpe(R = R, n = n) sharpes <- sharpes[!is.na(sharpes[,1]),] chart.TimeSeries(sharpes, legend.loc="topleft", main=paste("Rolling", n, "period Sharpe Ratio"), date.format="%Y", yaxis=FALSE, ylab="Sharpe Ratio", auto.grid=FALSE, ...) meltedSharpes <- do.call(c, data.frame(sharpes)) axisLabels <- pretty(meltedSharpes, n = 10) axisLabels <- unique(round(axisLabels, 1)) axisLabels <- axisLabels[axisLabels > min(axisLabels) & axisLabels < max(axisLabels)] axis(side=2, at=axisLabels, label=axisLabels, las=1) }
rolling_sharperatio <- as.data.table(runSharpe(as.xts.data.table(returns[, .(Date, Strategy)]), n = 252 / 2)) datatable(rolling_sharperatio, rownames = FALSE, escape = FALSE, extensions = 'Buttons', options = list(dom = 'Blfrtip', buttons = c('copy', 'csv', 'excel', 'pdf', 'print'), lengthMenu = list(c(10,25,50,-1), c(10,25,50,"All"))))
ggplot(rolling_sharperatio, aes(x = index, y = Strategy)) + geom_line(color = "blue")
# cors <- roll_cor(strategy$Strategy, benchmark$Benchmrk, width = 22L * 6) roll_cors <- roll_cor(as.matrix(equity_curve[, 2:3]), width = 22L * 6) cors <- roll_cors[1, 2, ] std_strategy <- roll_sd(as.matrix(equity_curve[, 2]), width = 22L * 6) std_banchmark <- roll_sd(as.matrix(equity_curve[, 3]), width = 22L * 6) roll_beta <- cors * (std_strategy / std_banchmark) plot(roll_beta)
# orders <- rbindlist(result$Orders, fill = TRUE) # head(result$Charts$`Strategy Equity`$Series$Equity$Values)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.