library(shiny) library(shinydashboard) library(shinydashboardPlus) library(shinyWidgets) library(shinycssloaders) library(DT) library(plotly) library(scico) library(ggthemes) library(scales) library(stringr) library(wesanderson) library(data.table) library(dtplyr) library(parallel) library(googlesheets4) # devtools::install_github("woobe/Rnumerai") library(Rnumerai) # Options options(encoding = "UTF-8") # Pre-download all usernames options(timeout = max(1000, getOption("timeout"))) ls_username <- sort(get_leaderboard()$username) # Prepare survey results data if (TRUE) { # Download gs4_deauth() d_survey_raw <- read_sheet(ss = "https://docs.google.com/spreadsheets/d/18AM4RkG5KiK3TlDGMx0z7X5Y5-eQE9kgLXM9ng_yXUk", sheet = "Form responses 1") %>% data.table() # Rename colnames(d_survey_raw) <- c("timestamp", "country", "comments") # Summarise d_survey_summary <- d_survey_raw %>% lazy_dt() %>% group_by(country) %>% summarise(count = n()) %>% arrange(desc(count), country) %>% as.data.table() } # ============================================================================== # Helper Functions # ============================================================================== # Download raw data download_raw_data <- function(model_name) { # Download data from Numerai d_raw <- round_model_performances(model_name) # Remove rows without CORR d_raw <- d_raw[!is.na(d_raw$corrWMetamodel), ] # Add the model name d_raw$model <- model_name # Return return(as.data.table(d_raw)) } # Reformat reformat_data <- function(d_raw) { # Keep some columns only col_keep <- c("model", "roundNumber", "roundOpenTime", "roundResolveTime", "roundResolved", "selectedStakeValue", "corr20V2", "corr20V2Percentile", "fncV3", "fncV3Percentile", "tc", "tcPercentile", "mmc", "mmcPercentile", "corrWMetamodel", "apcwnm", "mcwnm", "roundPayoutFactor", "payout") d_munged <- d_raw[, col_keep, with = FALSE] # Date d_munged[, roundOpenTime := as.Date(roundOpenTime)] d_munged[, roundResolveTime := as.Date(roundResolveTime)] # Reformat percentile d_munged[, corr20V2Percentile := round(corr20V2Percentile * 100, 6)] d_munged[, fncV3Percentile := round(fncV3Percentile * 100, 6)] d_munged[, tcPercentile := round(tcPercentile * 100, 6)] d_munged[, mmcPercentile := round(mmcPercentile * 100, 6)] # Rename columns colnames(d_munged) <- c("model", "round", "date_open", "date_resolved", "resolved", "stake", "corrV2", "corrV2_pct", "fncV3", "fncV3_pct", "tc", "tc_pct", "mmc", "mmc_pct", "corr_meta", "apcwnm", "mcwnm", "pay_ftr", "payout") # Return return(d_munged) } # Generate Colour Palette gen_custom_palette <- function(ls_model) { # Extract info n_limit <- 5 n_coluor <- length(unique(ls_model)) n_pal_rep <- ceiling(n_coluor / n_limit) wes_pal_themes <- rep(c("Cavalcanti1", "Darjeeling1"), n_pal_rep) # Generate custom_palette <- c() for (n_pal in 1:n_pal_rep) { tmp_pal_name <- wes_pal_themes[n_pal] tmp_pal <- wesanderson::wes_palette(name = tmp_pal_name, n = n_limit, type = "continuous") custom_palette <- c(custom_palette, tmp_pal) } # Trim and return return(custom_palette[1:n_coluor]) } # ============================================================================== # UI # ============================================================================== ui <- shinydashboardPlus::dashboardPage( title = "Shiny Numerati", skin = "black-light", options = list(sidebarExpandOnHover = TRUE), header = shinydashboardPlus::dashboardHeader( title = "✨ Shiny Numerati", userOutput("user") ), # ============================================================================ # Sidebar # ============================================================================ sidebar = shinydashboardPlus::dashboardSidebar( id = "sidebar", sidebarMenu( menuItem(text = "Start Here", tabName = "start", icon = icon("play")), menuItem(text = "Performance Summary", tabName = "performance", icon = icon("line-chart")), # icon("credit-card") menuItem(text = "Raw Data", tabName = "raw_data", icon = icon("download")), menuItem(text = "Community Events", tabName = "community", icon = icon("users")), menuItem(text = "About", tabName = "about", icon = icon("question-circle")) ), minified = TRUE, collapsed = FALSE ), # ============================================================================ # Main Body # ============================================================================ body = dashboardBody( tabItems( # ======================================================================== # Start Here # ======================================================================== tabItem(tabName = "start", fluidPage( # ============================================================== # Special script to keep the session alive for a bit longer # ============================================================== tags$head( HTML( " " ) ), # ============================================================== # First Page # ============================================================== markdown("# **Shiny Numerati**"), markdown("### Community Dashboard for the Numerai Classic Tournament"), br(), fluidRow( column(6, markdown("## **Step 1: Select Your Models**"), markdown("### First, click this ⬇"), pickerInput(inputId = "model", label = " ", choices = ls_username, multiple = TRUE, width = "100%", options = list( `title` = "---------->>> HERE <<<----------", `header` = "Notes: 1) Use the search box below to find and select your models. 2) Use 'Select All' for quick selection.", size = 20, `actions-box` = TRUE, `live-search` = TRUE, `live-search-placeholder` = "For example, try lgbm_v4 or integration_test", `virtual-scroll` = TRUE, `multiple-separator` = ", ", `selected-text-format`= "count > 3", `count-selected-text` = "{0} models selected (out of {1})", `deselect-all-text` = "Deselect All", `select-all-text` = "Select All" ) ) ), column(6, markdown("## **Step 2: Download Data**"), markdown("### Next, click this ⬇ (it may take a while)"), br(), actionBttn(inputId = "button_download", label = "Download Data from Numerai", color = "primary", icon = icon("cloud-download"), style = "gradient", block = TRUE ) ) ), br(), h3(strong(textOutput(outputId = "text_download"))), verbatimTextOutput(outputId = "print_download"), br(), h3(strong(textOutput(outputId = "text_preview"))), shinycssloaders::withSpinner(DTOutput("dt_model")), br(), h2(strong(textOutput(outputId = "text_next"))), h3(textOutput(outputId = "text_note")), br() ) ), # ======================================================================== # Payout Summary # ======================================================================== tabItem(tabName = "performance", fluidPage( markdown("# **Performance Summary**"), markdown("### Remember to refresh the charts after making changes to model selection or settings below."), markdown("### **NOTE**: the charts may take a while to render if you have selected a lot of models."), br(), fluidRow( column(6, markdown("## **Step 4: Adjust the Filter**"), sliderInput(inputId = "range_round", label = "Numerai Classic Tournament Rounds", width = "100%", step = 1, min = 168, # first tournament round max = Rnumerai::get_current_round(), # note: daily payouts from round 474 value = c(496, Rnumerai::get_current_round()) ) ), column(6, markdown("## **Step 5: Generate Summary**"), br(), actionBttn(inputId = "button_filter", label = "Generate / Refresh", color = "primary", icon = icon("refresh"), style = "gradient", block = TRUE) ) ), # end of fluidRow br(), tabsetPanel(type = "tabs", # First Page - All KPIs tabPanel("KPI (All)", br(), h3(strong(textOutput(outputId = "text_performance_chart"))), h4(textOutput(outputId = "text_performance_chart_note")), br(), # Controls fluidRow( column(6, markdown("#### **Pick ONE of the KPIs:**"), pickerInput( inputId = "kpi_choice", choices = c("MMCv2: The Latest and the Greatest MMC", "CORRv2: CORRelation with target cyrus_v4_20", "TC: True Contribtuion to the hedge fund's returns", "FNCv3: Feature Neutral Correlation with respect to the FNCv3 features", # "CORJ60: CORRelation with target Jerome_v4_60", # add this later "Percentile: MMCv2", "Percentile: CORRv2", "Percentile: TC", "Percentile: FNCv3", "CWMM: Correlation With the Meta Model", "MCWNM: Maximum Correlation With Numerai Models staked at least 10 NMR", "APCWNM: Average Pairwise Correlation With Numerai Models staked at least 10 NMR", "Score Multipliers: 0.5 x CORRv2 + 2.0 x MMCv2", "Score Multipliers: 0.5 x CORRv2", "Score Multipliers: 1.5 x CORRv2", "Score Multipliers: 2.0 x CORRv2", "Score Multipliers: 2.0 x CORRv2 + 0.5 x TC", "Score Multipliers: 2.0 x CORRv2 + 1.0 x TC", "Payout", "Rate of Return (%): Payout / Stake x 100"), multiple = FALSE, width = "95%") ), column(2, markdown("#### **Cumulative**"), switchInput( inputId = "kpi_cumulative", onLabel = "Yes", offLabel = "No", value = TRUE) ), column(2, markdown("#### **Hide Pending**"), switchInput( inputId = "kpi_hide_pending", onLabel = "Yes", offLabel = "No", value = FALSE) ), column(2, markdown("#### **Facet**"), switchInput( inputId = "kpi_facet", onLabel = "Yes", offLabel = "No", value = FALSE) ) ), h4(strong(textOutput(outputId = "text_performance_chart_data"))), br(), DTOutput("dt_kpi"), br(), br(), h4(strong(textOutput(outputId = "text_performance_chart_title"))), fluidRow(column(12, plotlyOutput("plot_kpi"))), br() ), tabPanel("KPI (C&T)", br(), h3(strong(textOutput(outputId = "text_performance_models"))), h4(textOutput(outputId = "text_performance_models_note")), br(), fluidRow( column(width = 6, plotlyOutput("plot_performance_avg")), column(width = 6, plotlyOutput("plot_performance_sharpe")) ), br(), br(), br(), fluidRow(DTOutput("dt_performance_summary"), br(), markdown("#### **Notes**: - **avg_corrV2**: Average `CORRv2` - **sharpe_corrV2**: Sharpe Ratio of `CORRv2` - **avg_tc**: Average True Contribution (`TC`) - **sharpe_tc**: Sharpe Ratio of True Contribution (`TC`) - **avg_2C1T**: Average `2xCORRv2 + 1xTC` - **sharpe_2C1T**: Sharpe Ratio of `2xCORRv2 + 1xTC` "), br() ), br() ), tabPanel("Payout (Overview)", br(), h3(strong(textOutput(outputId = "text_payout_overview"))), br(), fluidRow( class = "text-center", valueBoxOutput("payout_n_round_resolved", width = 3), valueBoxOutput("payout_resolved", width = 3), valueBoxOutput("payout_average_resolved", width = 3), valueBoxOutput("payout_avg_ror_resolved", width = 3), valueBoxOutput("payout_n_round_pending", width = 3), valueBoxOutput("payout_pending", width = 3), valueBoxOutput("payout_average_pending", width = 3), valueBoxOutput("payout_avg_ror_pending", width = 3), valueBoxOutput("payout_n_round", width = 3), valueBoxOutput("payout_total", width = 3), valueBoxOutput("payout_average", width = 3), valueBoxOutput("payout_avg_ror", width = 3) ), br(), shinycssloaders::withSpinner(plotlyOutput("plot_payout_net")), br() ), tabPanel("Payout (Rounds)", br(), h3(strong(textOutput(outputId = "text_payout_rnd"))), br(), DTOutput("dt_payout_summary"), br() ), tabPanel("Payout (Models)", br(), h3(strong(textOutput(outputId = "text_payout_ind"))), br(), DTOutput("dt_model_payout_summary"), br() ), tabPanel("Payout (Sim)", br(), h3(strong(textOutput(outputId = "text_payout_sim"))), br(), # markdown("![new_tc_change](https://i.ibb.co/XjKwtzr/screenshot-2023-10-05-at-10.png)"), markdown("![new_mmc](https://forum.numer.ai/uploads/default/optimized/2X/0/0b04785bd7167ff261f26325bc926c107398e26a_2_1035x729.jpeg)"), br(), markdown("#### **Notes**: - **sum_pay**: Sum of Payouts - **shp_pay**: Sharpe Ratio of Payouts - **1C3T**: 1xCORRv2 + 3xTC (Original Degen Mode) - **1C0T**: 1xCORRv2 + 0xTC (Until the End of 2023) - **2C0T**: 2xCORRv2 + 0xTC (Until the End of 2023) - **2C1T**: 2xCORRv2 + 1xTC (Until the End of 2023) - **05C2M**: 0.5xCORRv2 + 2xMMCv2 (**New Payout Mode**) "), br(), markdown("### **Payout Simulation (Overall)**"), DTOutput("dt_payout_sim_overall"), br(), br(), markdown("### **Payout Simulation (Individual Models)**"), br(), DTOutput("dt_payout_sim_model"), br() ), tabPanel("Payout Chart (Rounds)", br(), h3(strong(textOutput(outputId = "text_payout_all_models"))), br(), shinycssloaders::withSpinner(plotlyOutput("plot_payout_stacked")), br() ), tabPanel("Payout Chart (Models)", br(), h3(strong(textOutput(outputId = "text_payout_ind_models"))), br(), shinycssloaders::withSpinner(plotlyOutput("plot_payout_individual")), br() ), tabPanel("KPI (x~y)", br(), markdown("![image](https://media.giphy.com/media/cftSzNoCTfSyAWctcl/giphy.gif)"), br(), # Controls ============================================================================ fluidRow( column(6, markdown("#### **X-Axis:**"), pickerInput( inputId = "kpi_xy_x", choices = c("CORRv2: CORRelation with target cyrus_v4_20", "MMCv2: The Latest and the Greatest MMC", "TC: True Contribtuion to the hedge fund's returns", "FNCv3: Feature Neutral Correlation with respect to the FNCv3 features", "Percentile: MMCv2", "Percentile: CORRv2", "Percentile: TC", "Percentile: FNCv3", "CWMM: Correlation With the Meta Model", "MCWNM: Maximum Correlation With Numerai Models staked at least 10 NMR", "APCWNM: Average Pairwise Correlation With Numerai Models staked at least 10 NMR"), multiple = FALSE, width = "95%") ), column(6, markdown("#### **Y-Axis:**"), pickerInput( inputId = "kpi_xy_y", choices = c("MMCv2: The Latest and the Greatest MMC", "CORRv2: CORRelation with target cyrus_v4_20", "TC: True Contribtuion to the hedge fund's returns", "FNCv3: Feature Neutral Correlation with respect to the FNCv3 features", "Percentile: MMCv2", "Percentile: CORRv2", "Percentile: TC", "Percentile: FNCv3", "CWMM: Correlation With the Meta Model", "MCWNM: Maximum Correlation With Numerai Models staked at least 10 NMR", "APCWNM: Average Pairwise Correlation With Numerai Models staked at least 10 NMR"), multiple = FALSE, width = "95%") ), column(2, markdown("#### **Control 1**"), switchInput( inputId = "kpi_xy_ctrl_1", onLabel = "Yes", offLabel = "No", value = TRUE) ), column(2, markdown("#### **Control 2**"), switchInput( inputId = "kpi_xy_ctrl_2", onLabel = "Yes", offLabel = "No", value = FALSE) ), column(2, markdown("#### **Control 3**"), switchInput( inputId = "kpi_xy_ctrl_3", onLabel = "Yes", offLabel = "No", value = FALSE) ), column(2, markdown("#### **Control 4**"), switchInput( inputId = "kpi_xy_ctrl_4", onLabel = "Yes", offLabel = "No", value = FALSE) ), column(2, markdown("#### **Control 5**"), switchInput( inputId = "kpi_xy_ctrl_5", onLabel = "Yes", offLabel = "No", value = FALSE) ), column(2, markdown("#### **Control 6**"), switchInput( inputId = "kpi_xy_ctrl_6", onLabel = "Yes", offLabel = "No", value = FALSE) ) ), br() ) ) # end of tabsetPanel ) # end of fluidPage ), # ======================================================================== # Raw Data # ======================================================================== tabItem(tabName = "raw_data", markdown("# **Download Raw Data**"), markdown("### Wanna run your own analysis? No problem."), markdown("### Remember to select your model(s) first."), br(), fluidRow( column(6, downloadBttn(outputId = "download_raw", label = "Download Raw Data CSV", icon = icon("cloud-download"), style = "gradient", block = T) ) ) ), # ======================================================================== # Community # ======================================================================== tabItem(tabName = "community", fluidPage( markdown("# **Numerai Coummunity**"), br(), tabsetPanel(type = "tabs", # First Page - About tabPanel("About", markdown("## **Around the World with Numeratis**"), markdown("### **Overview** NumerCon (2022) and the time I spent with my fellow Numeratis were the exact “booster jab” I needed to wake the “meetup organizer Joe” up after two crazy years of COVID and hibernation in a man cave. After NumerCon, I am confident that 1) many countries are now ready for in-person tech events and 2) we need to bring this fantastic in-person experience to more Numeratis around the world. So, fam, here is my [proposal](https://forum.numer.ai/t/proposal-around-the-world-with-numeratis) for **Global Numerai Community Meetups**. "), markdown("### **Goals** - **Demystifying Numerai** - many top data scientists out there are just not too sure about the crypto elements and/or the tournament format. Speaking with and listening to long-term participants can be an effective way to build trust. This was exactly how I learned to trust Numerai after watching Jon’s Office Hours with Arbitrage. - **Knowledge sharing and brainstorming** - although we may not share all our secret sauce, meetups are great opportunities to bounce ideas off each other and may lead to new models with crazily high TC. - **Face-to-face discussions between Numeratis and Numerai team** - a lot more than just asking the team “wen scores” in person, these events can provide a quick and direct feedback loop for both participants and the Numerai team. "), markdown("### **What's Here?** - Materials (slides and videos) from previous events - Survey results for upcoming events - Memes and (de)GenAI stuff from the community ### **Enjoy :)** ") ), # Second Page - Materials tabPanel("Materials", fluidRow( column(10, htmltools::includeMarkdown('https://raw.githubusercontent.com/councilofelders/meetups/master/README.md') )) ), # Third Page - Survey Results tabPanel("Survey Results", markdown("## **Survey for Upcoming Events**"), br(), markdown("Hello everyone! We’re reaching out to gather **anonymous data** on your location, which will greatly assist us in planning future Numerai community events. Your support in providing this information would be much appreciated. You can find the latest survey results below. Here is the [**Google Form**](https://forms.gle/1USUa7YCn2EgZG3NA). Please share it with your fellow Numeratis. Thank you! "), fluidRow( column(4, markdown("### **Summary**"), br(), DTOutput("dt_survey_summary") ), column(8, markdown("### **Raw Data**"), br(), DTOutput("dt_survey_raw")) ) ), # 4th Page - Memes tabPanel("Memes", markdown("![image](https://media.giphy.com/media/cftSzNoCTfSyAWctcl/giphy.gif)") ) ) # end of tabsetPanel ) # end of fluidPage ), # ======================================================================== # About # ======================================================================== tabItem(tabName = "about", markdown("# **About this App**"), markdown('### Yet another Numerai community dashboard by Jo-fai Chow.'), br(), markdown("## **Acknowledgements**"), markdown("- #### This hobby project was inspired by Rajiv's shiny-kmeans on 🤗 Spaces."), markdown('- #### The Rnumerai package from Omni Analytics Group.'), br(), markdown("## **Source Code**"), markdown("- #### GitHub: https://github.com/woobe/shiny-numerati"), markdown("- #### Hugging Face: https://huggingface.co/spaces/jofaichow/shiny-numerati/tree/main"), br(), markdown("## **Changelog**"), markdown( " - #### **0.1.0** — First prototype with an interactive table output - #### **0.1.1** — Added a functional `Payout Summary` page - #### **0.1.2** — `Payout Summary` layout updates - #### **0.1.3** — Added `Raw Data` - #### **0.1.4** — Various improvements in `Payout Summary` - #### **0.1.5** — Replaced `corrV1` with `corrV2` - #### **0.1.6** — Added `apcwnm` and `mcwnm` - #### **0.1.7** — Added CoE Meetup GitHub page to `Community` - #### **0.1.8** — Various improvements in `Payout Summary` - #### **0.1.9** — Added `Payout Sim` based on new Corr and TC multipier settings - #### **0.2.0** — Replaced `Payout Summary` with `Performance Summary`. Added KPIs summary - #### **0.2.1** — Added `KPI (All)` - #### **0.2.2** — Sped up chart rendering with `toWebGL()` - #### **0.2.3** — Added new `MMC` - Ref: https://forum.numer.ai/t/changing-scoring-payouts-again-to-mmc-only - #### **0.2.4** — Added `MMC` to `Payout Sim` - #### **0.2.5** — Added more features related to MMC - #### **0.2.6** — Added survey results - Ref: https://forum.numer.ai/t/around-the-world-with-numeratis-survey-for-upcoming-events "), br(), markdown("## **Session Info**"), verbatimTextOutput(outputId = "session_info"), br(), textOutput("keepAlive") # trick to keep session alive ) # ======================================================================== ) # end of tabItems ), footer = shinydashboardPlus::dashboardFooter( left = "Powered by ❤️, ☕, Shiny, and 🤗 Spaces", right = paste0("Version 0.2.6")) ) # ============================================================================== # Server # ============================================================================== server <- function(input, output) { # About Joe output$user <- renderUser({ dashboardUser( name = "JC", image = "https://numerai-public-images.s3.amazonaws.com/profile_images/aijoe_v5_compressed-iJWEo1WeHkpH.jpg", subtitle = "@matlabulous", footer = p('"THE NMR LIFE CHOSE ME."', class = 'text-center') ) }) # ============================================================================ # Reactive: Data # ============================================================================ react_ls_model <- eventReactive(input$button_download, {sort(input$model)}) react_d_raw <- eventReactive( input$button_download, { # Parallelised download d_raw <- rbindlist(mclapply(X = input$model, FUN = download_raw_data, mc.cores = detectCores())) # Return d_raw } ) output$dt_model <- DT::renderDT({ # Raw Data d_raw <- react_d_raw() # Reformat d_munged <- reformat_data(d_raw) # Main DT DT::datatable( # Data d_munged, # Other Options rownames = FALSE, extensions = "Buttons", options = list( dom = 'Bflrtip', # https://datatables.net/reference/option/dom buttons = list('csv', 'excel', 'copy', 'print'), # https://rstudio.github.io/DT/003-tabletools-buttons.html order = list(list(0, 'asc'), list(1, 'asc')), pageLength = 10, lengthMenu = c(10, 20, 100, 500, 1000, 50000), columnDefs = list(list(className = 'dt-center', targets = "_all"))) ) |> # Reformat individual columns formatRound(columns = c("corrV2", "tc", "fncV3", "corr_meta", "pay_ftr", "mmc"), digits = 4) |> formatRound(columns = c("apcwnm", "mcwnm"), digits = 4) |> formatRound(columns = c("corrV2_pct", "tc_pct", "fncV3_pct", "mmc_pct"), digits = 1) |> formatRound(columns = c("stake", "payout"), digits = 2) |> formatStyle(columns = c("model"), fontWeight = "bold") |> formatStyle(columns = c("stake"), fontWeight = "bold", color = styleInterval(cuts = -1e-15, values = c("#D24141", "#2196F3"))) |> formatStyle(columns = c("corrV2", "fncV3", "mmc"), color = styleInterval(cuts = -1e-15, values = c("#D24141", "black"))) |> formatStyle(columns = c("tc"), color = styleInterval(cuts = -1e-15, values = c("#D24141", "#A278DC"))) |> formatStyle(columns = c("corrV2_pct", "tc_pct", "fncV3_pct", "mmc_pct"), color = styleInterval(cuts = c(1, 5, 15, 85, 95, 99), values = c("#692020", "#9A2F2F", "#D24141", "#D1D1D1", # light grey "#00A800", "#007000", "#003700"))) |> formatStyle(columns = c("payout"), fontWeight = "bold", color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) }) # ============================================================================ # Static: Survey Data # ============================================================================ output$dt_survey_summary <- DT::renderDT({ # Return DT::datatable( # Data d_survey_summary, # Other Options rownames = FALSE, # extensions = "Buttons", options = list( dom = 'Blrtip', # https://datatables.net/reference/option/dom pageLength = 20, lengthMenu = c(10, 20, 100, 500) ) ) }) output$dt_survey_raw <- DT::renderDT({ # Return DT::datatable( # Data d_survey_raw, # Other Options rownames = FALSE, # extensions = "Buttons", options = list( dom = 'Blrtip', # https://datatables.net/reference/option/dom pageLength = 20, lengthMenu = c(10, 20, 100, 500) ) ) }) # ============================================================================ # Reactive: Text Printout # ============================================================================ output$print_download <- renderPrint({react_ls_model()}) output$text_download <- renderText({ if (length(react_ls_model()) >= 1) "Your Selection:" else " " }) output$text_preview <- renderText({ if (length(react_ls_model()) >= 1) "Data Preview:" else " " }) output$text_next <- renderText({ if (length(react_ls_model()) >= 1) "Step 3: Performance Summary (see ←)" else " " }) output$text_note <- renderText({ if (length(react_ls_model()) >= 1) "Note: you can also download [Raw Data] and check out our [Community Events] (see ←)" else " " }) # ============================================================================ # Reactive: filtering data for all charts # ============================================================================ react_d_filter <- eventReactive( input$button_filter, { # Reformat and Filter d_filter <- reformat_data(react_d_raw()) d_filter <- d_filter[round >= input$range_round[1], ] d_filter <- d_filter[round <= input$range_round[2], ] # Return d_filter }) react_d_payout_summary <- eventReactive( input$button_filter, { # Summarise payout d_smry <- react_d_filter() |> lazy_dt() |> filter(pay_ftr > 0) |> filter(stake > 0) |> group_by(round, date_open, date_resolved, resolved) |> summarise(staked_models = n(), total_stake = sum(stake, na.rm = T), net_payout = sum(payout, na.rm = T)) |> as.data.table() d_smry$rate_of_return <- (d_smry$net_payout / d_smry$total_stake) * 100 # Return d_smry }) react_d_model_payout_summary <- eventReactive( input$button_filter, { # Get filtered data # d_smry <- as.data.table(react_d_filter() |> filter(pay_ftr > 0)) d_smry <- react_d_filter() |> lazy_dt() |> filter(pay_ftr > 0) |> filter(stake > 0) |> as.data.table() # Calculate rate of return (%) d_smry[, rate_of_return_percent := payout / stake * 100] # Summarise d_smry <- d_smry |> lazy_dt() |> group_by(model) |> summarise(staked_rounds = n(), net_payout = sum(payout, na.rm = T), avg_payout = mean(payout, na.rm = T), avg_rate_of_return_percent = mean(rate_of_return_percent, na.rm = T), sharpe_rate_of_return = mean(rate_of_return_percent, na.rm = T) / sd(rate_of_return_percent, na.rm = T) ) |> as.data.table() # Return d_smry }) react_d_payout_sim_model <- eventReactive( input$button_filter, { # Get filtered data # d_smry <- as.data.table(react_d_filter() |> filter(pay_ftr > 0)) d_payout <- react_d_filter() |> lazy_dt() |> filter(pay_ftr > 0) |> filter(stake > 0) |> as.data.table() # Apply clip to corrV2 d_payout[, corrV2_final := corrV2] d_payout[corrV2 > 0.25, corrV2_final := 0.25] d_payout[corrV2 < -0.25, corrV2_final := -0.25] # Apply clip to tc d_payout[, tc_final := tc] d_payout[tc > 0.25, tc_final := 0.25] d_payout[tc < -0.25, tc_final := -0.25] # Calculate different payout d_payout[, payout_1C0T := (corrV2_final) * stake * pay_ftr] d_payout[, payout_2C0T := (2*corrV2_final) * stake * pay_ftr] d_payout[, payout_2C1T := (2*corrV2_final + tc_final) * stake * pay_ftr] d_payout[, payout_1C3T := (corrV2_final + 3*tc_final) * stake * pay_ftr] d_payout[, payout_05C2M := (0.5*corrV2_final + 2*mmc) * stake * pay_ftr] # Summarise d_payout_smry <- d_payout |> lazy_dt() |> group_by(model) |> summarise( rounds = n(), sum_pay_1C0T = sum(payout_1C0T, na.rm = T), sum_pay_2C0T = sum(payout_2C0T, na.rm = T), sum_pay_2C1T = sum(payout_2C1T, na.rm = T), sum_pay_1C3T = sum(payout_1C3T, na.rm = T), sum_pay_05C2M = sum(payout_05C2M, na.rm = T), shp_pay_1C0T = mean(payout_1C0T, na.rm = T) / sd(payout_1C0T, na.rm = T), shp_pay_2C0T = mean(payout_2C0T, na.rm = T) / sd(payout_2C0T, na.rm = T), shp_pay_2C1T = mean(payout_2C1T, na.rm = T) / sd(payout_2C1T, na.rm = T), shp_pay_1C3T = mean(payout_1C3T, na.rm = T) / sd(payout_1C3T, na.rm = T), shp_pay_05C2M = mean(payout_05C2M, na.rm = T) / sd(payout_05C2M, na.rm = T) ) |> as.data.table() # Return d_payout_smry }) react_d_payout_sim_overall <- eventReactive( input$button_filter, { # Get filtered data # d_payout <- as.data.table(react_d_filter() |> filter(pay_ftr > 0)) d_payout <- react_d_filter() |> lazy_dt() |> filter(pay_ftr > 0) |> filter(stake > 0) |> as.data.table() # Apply clip to corrV2 d_payout[, corrV2_final := corrV2] d_payout[corrV2 > 0.25, corrV2_final := 0.25] d_payout[corrV2 < -0.25, corrV2_final := -0.25] # Apply clip to tc d_payout[, tc_final := tc] d_payout[tc > 0.25, tc_final := 0.25] d_payout[tc < -0.25, tc_final := -0.25] # Calculate different payout d_payout[, payout_1C0T := (corrV2_final) * stake * pay_ftr] d_payout[, payout_2C0T := (2*corrV2_final) * stake * pay_ftr] d_payout[, payout_2C1T := (2*corrV2_final + tc_final) * stake * pay_ftr] d_payout[, payout_1C3T := (corrV2_final + 3*tc_final) * stake * pay_ftr] d_payout[, payout_05C2M := (0.5*corrV2 + 2*mmc) * stake * pay_ftr] # Summarise d_payout_smry <- d_payout |> lazy_dt() |> summarise( sum_pay_1C0T = sum(payout_1C0T, na.rm = T), sum_pay_2C0T = sum(payout_2C0T, na.rm = T), sum_pay_2C1T = sum(payout_2C1T, na.rm = T), sum_pay_1C3T = sum(payout_1C3T, na.rm = T), sum_pay_05C2M = sum(payout_05C2M, na.rm = T), shp_pay_1C0T = mean(payout_1C0T, na.rm = T) / sd(payout_1C0T, na.rm = T), shp_pay_2C0T = mean(payout_2C0T, na.rm = T) / sd(payout_2C0T, na.rm = T), shp_pay_2C1T = mean(payout_2C1T, na.rm = T) / sd(payout_2C1T, na.rm = T), shp_pay_1C3T = mean(payout_1C3T, na.rm = T) / sd(payout_1C3T, na.rm = T), shp_pay_05C2M = mean(payout_05C2M, na.rm = T) / sd(payout_05C2M, na.rm = T) ) |> as.data.table() # Return d_payout_smry }) react_d_performance_summary <- eventReactive( input$button_filter, { # Get filtered data d_pref <- as.data.table(react_d_filter()) # Add 2xCORRv2 + 1xTC d_pref[, twoC_oneT := 2*corrV2 + tc] # Calculate some high level stats d_pref <- d_pref |> lazy_dt() |> group_by(model) |> summarise(total_rounds = n(), avg_corrV2 = mean(corrV2, na.rm = T), sharpe_corrV2 = mean(corrV2, na.rm = T) / sd(corrV2, na.rm = T), # mdd_corrV2 = maxdrawdown(corrV2), avg_tc = mean(tc, na.rm = T), sharpe_tc = mean(tc, na.rm = T) / sd(tc, na.rm = T), # mdd_tc = maxdrawdown(tc), avg_2C1T = mean(twoC_oneT, na.rm = T), sharpe_2C1T = mean(twoC_oneT, na.rm = T) / sd(tc, na.rm = T) # mdd_2C1T = maxdrawdown(twoC_oneT) ) |> as.data.table() # Return d_pref }) react_d_kpi <- eventReactive( input$button_filter, { # Get filtered data d_pref <- as.data.table(react_d_filter()) # Hide Pending? if (input$kpi_hide_pending) d_pref <- d_pref[resolved == TRUE] # Add Rate of Return d_pref[stake >0, rate_of_return := payout / stake * 100] # Extract Raw KPI if (input$kpi_choice == "MMCv2: The Latest and the Greatest MMC") d_pref[, KPI := mmc] if (input$kpi_choice == "CORRv2: CORRelation with target cyrus_v4_20") d_pref[, KPI := corrV2] if (input$kpi_choice == "TC: True Contribtuion to the hedge fund's returns") d_pref[, KPI := tc] if (input$kpi_choice == "FNCv3: Feature Neutral Correlation with respect to the FNCv3 features") d_pref[, KPI := fncV3] if (input$kpi_choice == "CWMM: Correlation With the Meta Model") d_pref[, KPI := corr_meta] if (input$kpi_choice == "MCWNM: Maximum Correlation With Numerai Models staked at least 10 NMR") d_pref[, KPI := mcwnm] if (input$kpi_choice == "APCWNM: Average Pairwise Correlation With Numerai Models staked at least 10 NMR") d_pref[, KPI := apcwnm] # Calculate Score Multiplies if (input$kpi_choice == "Score Multipliers: 0.5 x CORRv2 + 2.0 x MMCv2") d_pref[, KPI := 0.5 * corrV2 + 2.0 * mmc] if (input$kpi_choice == "Score Multipliers: 0.5 x CORRv2") d_pref[, KPI := 0.5 * corrV2] if (input$kpi_choice == "Score Multipliers: 1.5 x CORRv2") d_pref[, KPI := 1.5 * corrV2] if (input$kpi_choice == "Score Multipliers: 2.0 x CORRv2") d_pref[, KPI := 2.0 * corrV2] if (input$kpi_choice == "Score Multipliers: 2.0 x CORRv2 + 0.5 x TC") d_pref[, KPI := 2.0 * corrV2 + 0.5 * tc] if (input$kpi_choice == "Score Multipliers: 2.0 x CORRv2 + 1.0 x TC") d_pref[, KPI := 2.0 * corrV2 + 1.0 * tc] # Extract Percentile if (input$kpi_choice == "Percentile: MMCv2") d_pref[, KPI := mmc_pct] if (input$kpi_choice == "Percentile: CORRv2") d_pref[, KPI := corrV2_pct] if (input$kpi_choice == "Percentile: TC") d_pref[, KPI := tc_pct] if (input$kpi_choice == "Percentile: FNCv3") d_pref[, KPI := fncV3_pct] # Extract Payout info if (input$kpi_choice == "Payout") d_pref[, KPI := payout] if (input$kpi_choice == "Rate of Return (%): Payout / Stake x 100") d_pref[, KPI := rate_of_return] # Remove rows with NA (quick hack for now) d_pref <- d_pref[!is.na(KPI)] # Calculate Cumulative KPI (if needed) if (input$kpi_cumulative) { # Sort before doing cumsum setorderv(d_pref, c("model", "round")) # The data.table way d_pref[, KPI := cumsum(KPI), "model"] } # Trim and sort d_trim <- d_pref[, c("model", "round", "date_resolved", "resolved", "KPI")] setorderv(d_trim, c("model", "round")) # Return d_trim }) # ============================================================================ # Reactive: Payout Value Boxes # ============================================================================ output$text_payout_overview <- renderText({ if (nrow(react_d_filter()) >= 1) "Payout Summary (Overview)" else " " }) output$text_payout_rnd <- renderText({ if (nrow(react_d_filter()) >= 1) "Payout Summary (Tournament Rounds)" else " " }) output$text_payout_ind <- renderText({ if (nrow(react_d_filter()) >= 1) "Payout Summary (Individual Models)" else " " }) output$text_payout_all_models <- renderText({ if (nrow(react_d_filter()) >= 1) "Payout Summary Chart (All Models - Stacked)" else " " }) output$text_payout_ind_models <- renderText({ if (nrow(react_d_filter()) >= 1) "Payout Summary Chart (Individual Models)" else " " }) output$text_payout_sim <- renderText({ if (nrow(react_d_filter()) >= 1) "New Payout Simulation (NOTE: Experimental!)" else " " }) output$text_performance_models <- renderText({ if (nrow(react_d_filter()) >= 1) "KPI Analysis (CORRv2 and TC)" else " " }) output$text_performance_models_note <- renderText({ if (nrow(react_d_filter()) >= 1) "NOTE: You may want to find out which models have high CORRv2 Sharpe and high TC Sharpe." else " " }) output$text_performance_chart <- renderText({ if (nrow(react_d_filter()) >= 1) "KPI Analysis (Model Comparison)" else " " }) output$text_performance_chart_note <- renderText({ if (nrow(react_d_filter()) >= 1) "NOTE: Remember to refresh the chart (Step 5 ↑) after making any changes." else " " }) output$text_performance_chart_title <- renderText({ if (nrow(react_d_filter()) >= 1) "KPI Chart (Remember to Click the 'Refresh' Button ↑↑↑)" else " " }) output$text_performance_chart_data <- renderText({ if (nrow(react_d_filter()) >= 1) "KPI Data" else " " }) # ============================================================================ # Reactive valueBox outputs: Rounds # ============================================================================ output$payout_n_round_resolved <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = nrow(react_d_payout_summary()[resolved == TRUE & total_stake > 0, ]), subtitle = "Staked Rounds (Resolved)", color = "olive") }) output$payout_n_round_pending <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = nrow(react_d_payout_summary()[resolved == FALSE & total_stake > 0, ]), subtitle = "Staked Rounds (Pending)", color = "yellow") }) output$payout_n_round <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = nrow(react_d_payout_summary()[total_stake > 0, ]), subtitle = "Staked Rounds (All)", color = "light-blue") }) # ============================================================================ # Reactive valueBox outputs: Payouts # ============================================================================ output$payout_resolved <- renderValueBox({ valueBox(value = paste(as.character(format(round(sum(react_d_filter()[resolved == T, ]$payout, na.rm = T), 2), nsmall = 2)), "NMR"), subtitle = "Total Payout (Resolved)", color = "olive") }) output$payout_pending <- renderValueBox({ valueBox(value = paste(as.character(format(round(sum(react_d_filter()[resolved == F, ]$payout, na.rm = T), 2), nsmall = 2)), "NMR"), subtitle = "Total Payout (Pending)", color = "yellow") }) output$payout_total <- renderValueBox({ valueBox(value = paste(as.character(format(round(sum(react_d_filter()$payout, na.rm = T), 2), nsmall = 2)), "NMR"), subtitle = "Total Payout (All)", color = "light-blue") }) # ============================================================================ # Reactive valueBox outputs: Average Round Payouts # ============================================================================ output$payout_average_resolved <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = paste(as.character(format(round(mean(react_d_payout_summary()[resolved == T & total_stake > 0, ]$net_payout, na.rm = T), 2), nsmall = 2)), "NMR"), subtitle = "Avg. Round Payout (Resolved)", color = "olive") }) output$payout_average_pending <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = paste(as.character(format(round(mean(react_d_payout_summary()[resolved == F & total_stake > 0, ]$net_payout, na.rm = T), 2), nsmall = 2)), "NMR"), subtitle = "Avg. Round Payout (Pending)", color = "yellow") }) output$payout_average <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = paste(as.character(format(round(mean(react_d_payout_summary()[total_stake > 0, ]$net_payout, na.rm = T), 2), nsmall = 2)), "NMR"), subtitle = "Avg. Round Payout (All)", color = "light-blue") }) # ============================================================================ # Reactive valueBox outputs: Average Rate of Return # ============================================================================ output$payout_avg_ror_resolved <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = paste(as.character(format(round(mean(react_d_payout_summary()[resolved == T & total_stake > 0, ]$rate_of_return), 2), nsmall = 2)), "%"), subtitle = "Avg. Round ROR (Resolved)", color = "olive") }) output$payout_avg_ror_pending <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = paste(as.character(format(round(mean(react_d_payout_summary()[resolved == F & total_stake > 0, ]$rate_of_return), 2), nsmall = 2)), "%"), subtitle = "Avg. Round ROR (Pending)", color = "yellow") }) output$payout_avg_ror <- renderValueBox({ # Use rounds with stake > 0 only valueBox(value = paste(as.character(format(round(mean(react_d_payout_summary()[total_stake > 0, ]$rate_of_return), 2), nsmall = 2)), "%"), subtitle = "Avg. Round ROR (All)", color = "light-blue") }) # ============================================================================ # Reactive: Payout Charts # ============================================================================ # Net Payouts Bar Chart output$plot_payout_net <- renderPlotly({ # Data d_filter <- react_d_payout_summary() # Filter d_filter <- d_filter[total_stake > 0] # Divider (resolved vs pending) x_marker <- max(d_filter[resolved == TRUE]$round) + 0.5 y_marker <- max(d_filter$net_payout) # ggplot p <- ggplot(d_filter, aes(x = round, y = net_payout, fill = net_payout, text = paste("Round:", round, "\nRound Open Date:", date_open, "\nRound Resolved Date:", date_resolved, "\nRound Resolved?:", resolved, "\nPayout:", round(net_payout,2), "NMR"))) + geom_bar(position = "stack", stat = "identity") + theme( panel.border = element_rect(fill = 'transparent', color = "grey", linewidth = 0.25), panel.background = element_rect(fill = 'transparent'), plot.background = element_rect(fill = 'transparent', color = NA), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), strip.background = element_rect(fill = 'transparent'), strip.text = element_text(), strip.clip = "on", legend.background = element_rect(fill = 'transparent'), legend.box.background = element_rect(fill = 'transparent') ) + geom_vline(aes(xintercept = x_marker), linewidth = 0.25, color = "grey", linetype = "dashed") + geom_hline(aes(yintercept = 0), linewidth = 0.25, color = "grey") + annotate("text", x = x_marker, y = y_marker*1.2, label = "← Resolved vs. Pending →") + scale_fill_scico(palette = "vikO", direction = -1, midpoint = 0) + # scale_x_date(breaks = breaks_pretty(10), # labels = label_date_short(format = c("%Y", "%b", "%d"), sep = "\n") # ) + xlab("\nTournament Round") + ylab("Round Payout (NMR)") # Generate plotly ggplotly(p, height = 500, tooltip = "text") |> toWebGL() }) # Stacked Bar Chart output$plot_payout_stacked <- renderPlotly({ # Data d_filter <- react_d_filter() # Filter d_filter <- d_filter[stake > 0] # Divider (resolved vs pending) x_marker <- max(d_filter[resolved == TRUE]$round) + 0.5 # ggplot p <- ggplot(d_filter, aes(x = round, y = payout, fill = payout, text = paste("Model:", model, "\nRound:", round, "\nRound Open Date:", date_open, "\nRound Resolved Date:", date_resolved, "\nRound Resolved?:", resolved, "\nPayout:", round(payout,2), "NMR"))) + geom_bar(position = "stack", stat = "identity") + theme( panel.border = element_rect(fill = 'transparent', color = "grey", linewidth = 0.25), panel.background = element_rect(fill = 'transparent'), plot.background = element_rect(fill = 'transparent', color = NA), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), strip.background = element_rect(fill = 'transparent'), strip.text = element_text(), strip.clip = "on", legend.background = element_rect(fill = 'transparent'), legend.box.background = element_rect(fill = 'transparent') ) + geom_vline(aes(xintercept = x_marker), linewidth = 0.25, color = "grey", linetype = "dashed") + geom_hline(aes(yintercept = 0), linewidth = 0.25, color = "grey") + scale_fill_scico(palette = "vikO", direction = -1, midpoint = 0) + # scale_x_date(breaks = breaks_pretty(10), # labels = label_date_short(format = c("%Y", "%b", "%d"), sep = "\n") # ) + xlab("\nTournament Round") + ylab("Round Payout (NMR)") # Generate plotly ggplotly(p, height = 500, tooltip = "text") |> toWebGL() }) # Individual output$plot_payout_individual <- renderPlotly({ # Data d_filter <- react_d_filter() # Filter d_filter <- d_filter[stake > 0] # Get the number of unique models n_model <- length(unique(d_filter$model)) # Base plot p <- ggplot(d_filter, aes(x = round, y = payout, fill = payout, text = paste("Model:", model, "\nRound:", round, "\nRound Open Date:", date_open, "\nRound Resolved Date:", date_resolved, "\nRound Resolved:", resolved, "\nPayout:", round(payout,2), "NMR"))) + geom_bar(stat = "identity") + theme( panel.border = element_rect(fill = 'transparent', color = "grey", linewidth = 0.25), panel.background = element_rect(fill = 'transparent'), plot.background = element_rect(fill = 'transparent', color = NA), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), strip.background = element_rect(fill = 'transparent'), strip.text = element_text(), strip.clip = "on", legend.background = element_rect(fill = 'transparent'), legend.box.background = element_rect(fill = 'transparent'), axis.text.x = element_text(angle = 45, hjust = 1) ) + geom_hline(aes(yintercept = 0), linewidth = 0.25, color = "grey") + scale_fill_scico(palette = "vikO", direction = -1, midpoint = 0) + scale_x_continuous(breaks = breaks_pretty(5)) + xlab("\nTournament Round") + ylab("Payout (NMR)") # Facet setting # if ((n_model %% 4) == 0) { # p <- p + facet_wrap(. ~ model, ncol = 4, scales = "fixed") # } else if ((n_model %% 5) == 0) { # p <- p + facet_wrap(. ~ model, ncol = 5, scales = "fixed") # } else { # p <- p + facet_wrap(. ~ model, ncol = 6, scales = "fixed") # } p <- p + facet_wrap(. ~ model, ncol = 5, scales = "fixed") # fixed # Dynamic height adjustment height <- 500 # default minimum height if (n_model >= 10) height = 800 if (n_model >= 15) height = 1000 if (n_model >= 20) height = 1200 if (n_model >= 25) height = 1400 if (n_model >= 30) height = 1600 if (n_model >= 35) height = 1800 if (n_model >= 40) height = 2000 if (n_model >= 45) height = 2200 if (n_model >= 50) height = 2400 if (n_model >= 55) height = 2600 if (n_model >= 60) height = 2800 if (n_model >= 65) height = 3000 # Generate plotly ggplotly(p, height = height, tooltip = "text") |> toWebGL() }) # KPI Chart: Avg Corr vs. Avg TC output$plot_performance_avg <- renderPlotly({ # Data d_pref <- react_d_performance_summary() # Plot p_avg <- ggplot(d_pref, aes(x = avg_tc, y = avg_corrV2, text = paste("Model:", model, "\nAverage CORRv2:", round(avg_corrV2, 4), "\nAverage TC:", round(avg_tc, 4)) )) + geom_point() + theme( panel.border = element_rect(fill = 'transparent', color = "grey", linewidth = 0.25), panel.background = element_rect(fill = 'transparent'), plot.background = element_rect(fill = 'transparent', color = NA), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), strip.background = element_rect(fill = 'transparent'), strip.text = element_text(), strip.clip = "on", legend.background = element_rect(fill = 'transparent'), legend.box.background = element_rect(fill = 'transparent'), axis.text.x = element_text(angle = 45, hjust = 1) ) + scale_x_continuous(breaks = breaks_pretty(5)) + scale_y_continuous(breaks = breaks_pretty(5)) + xlab("\nAverage TC") + ylab("\nAverage CORRv2") # Add vline and hline if needed if (min(d_pref$avg_corrV2) <0) p_avg <- p_avg + geom_hline(aes(yintercept = 0), linewidth = 0.25, color = "grey", linetype = "dashed") if (min(d_pref$avg_tc) <0) p_avg <- p_avg + geom_vline(aes(xintercept = 0), linewidth = 0.25, color = "grey", linetype = "dashed") # Convert to Plotly ggplotly(p_avg, tooltip = "text") |> toWebGL() }) # KPI Chart: Corr Sharpe vs. TC Sharpe output$plot_performance_sharpe <- renderPlotly({ # Data d_pref <- react_d_performance_summary() # Plot p_sharpe <- ggplot(d_pref, aes(x = sharpe_tc, y = sharpe_corrV2, text = paste("Model:", model, "\nSharpe Ratio of CORRv2:", round(sharpe_corrV2, 4), "\nSharpe Ratio of TC:", round(sharpe_tc, 4)) )) + geom_point() + theme( panel.border = element_rect(fill = 'transparent', color = "grey", linewidth = 0.25), panel.background = element_rect(fill = 'transparent'), plot.background = element_rect(fill = 'transparent', color = NA), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), strip.background = element_rect(fill = 'transparent'), strip.text = element_text(), strip.clip = "on", legend.background = element_rect(fill = 'transparent'), legend.box.background = element_rect(fill = 'transparent'), axis.text.x = element_text(angle = 45, hjust = 1) ) + scale_x_continuous(breaks = breaks_pretty(5)) + scale_y_continuous(breaks = breaks_pretty(5)) + xlab("\nSharpe Ratio of TC") + ylab("\nSharpe Ratio of CORRv2") # Add vline and hline if needed if (min(d_pref$sharpe_corrV2) <0) p_sharpe <- p_sharpe + geom_hline(aes(yintercept = 0), linewidth = 0.25, color = "grey", linetype = "dashed") if (min(d_pref$sharpe_tc) <0) p_sharpe <- p_sharpe + geom_vline(aes(xintercept = 0), linewidth = 0.25, color = "grey", linetype = "dashed") # Convert to Plotly ggplotly(p_sharpe, tooltip = "text") |> toWebGL() }) # KPI Chart: All KPIs output$plot_kpi <- renderPlotly({ # Data d_kpi <- react_d_kpi() # Dynamic Labels if (input$kpi_choice == "MMCv2: The Latest and the Greatest MMC") y_label <- "mmc" if (input$kpi_choice == "CORRv2: CORRelation with target cyrus_v4_20") y_label <- "CORRv2" if (input$kpi_choice == "TC: True Contribtuion to the hedge fund's returns") y_label <- "TC" if (input$kpi_choice == "FNCv3: Feature Neutral Correlation with respect to the FNCv3 features") y_label <- "FNCv3" if (input$kpi_choice == "CWMM: Correlation With the Meta Model") y_label <- "CWMM" if (input$kpi_choice == "MCWNM: Maximum Correlation With Numerai Models staked at least 10 NMR") y_label <- "MCWNM" if (input$kpi_choice == "APCWNM: Average Pairwise Correlation With Numerai Models staked at least 10 NMR") y_label <- "APCWNM" if (input$kpi_choice == "Score Multipliers: 0.5 x CORRv2 + 2.0 x MMCv2") y_label <- "0.5 x CORRv2 + 2.0 x MMCv2" if (input$kpi_choice == "Score Multipliers: 0.5 x CORRv2") y_label <- "0.5 x CORRv2" if (input$kpi_choice == "Score Multipliers: 1.5 x CORRv2") y_label <- "1.5 x CORRv2" if (input$kpi_choice == "Score Multipliers: 2.0 x CORRv2") y_label <- "2.0 x CORRv2" if (input$kpi_choice == "Score Multipliers: 2.0 x CORRv2 + 0.5 x TC") y_label <- "2.0 x CORRv2 + 0.5 x TC" if (input$kpi_choice == "Score Multipliers: 2.0 x CORRv2 + 1.0 x TC") y_label <- "2.0 x CORRv2 + 1.0 x TC" if (input$kpi_choice == "Percentile: MMCv2") y_label <- "MMCv2 Percentile" if (input$kpi_choice == "Percentile: CORRv2") y_label <- "CORRv2 Percentile" if (input$kpi_choice == "Percentile: TC") y_label <- "TC Percentile" if (input$kpi_choice == "Percentile: FNCv3") y_label <- "FNCv3 Percentile" if (input$kpi_choice == "Payout") y_label <- "Payout (NMR)" if (input$kpi_choice == "Rate of Return (%): Payout / Stake x 100") y_label <- "Rate of Return (%)" # If cumulative if (input$kpi_cumulative) y_label <- paste("Cumulative", y_label) # Other settings y_min <- min(d_kpi$KPI) * 0.95 if (y_min > 0) y_min <- 0 y_max <- max(d_kpi$KPI) * 1.05 height <- 500 # default minimum height # Plot p <- ggplot(d_kpi, aes(x = round, y = KPI, ymin = y_min, ymax = y_max, color = model)) + geom_line() + theme( panel.border = element_rect(fill = 'transparent', color = "grey", linewidth = 0.25), panel.background = element_rect(fill = 'transparent'), plot.background = element_rect(fill = 'transparent', color = NA), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), strip.background = element_rect(fill = 'transparent'), strip.text = element_text(), strip.clip = "on", legend.background = element_rect(fill = 'transparent'), legend.box.background = element_rect(fill = 'transparent'), axis.text.x = element_text(angle = 45, hjust = 1) ) + scale_x_continuous(breaks = breaks_pretty(5)) + scale_y_continuous(breaks = breaks_pretty(5)) + geom_hline(aes(yintercept = 0), linewidth = 0.25, color = "grey", linetype = "dashed") + ylab(y_label) + xlab("\nTournament Round") # Facet wrap? if (input$kpi_facet) { # Extract no. of models n_model <- length(unique(d_kpi$model)) # Add facet_wrap p <- p + facet_wrap(. ~ model, ncol = 5, scales = "fixed") # fixed # Dynamic height adjustment if (n_model >= 10) height = 800 if (n_model >= 15) height = 1000 if (n_model >= 20) height = 1200 if (n_model >= 25) height = 1400 if (n_model >= 30) height = 1600 if (n_model >= 35) height = 1800 if (n_model >= 40) height = 2000 if (n_model >= 45) height = 2200 if (n_model >= 50) height = 2400 if (n_model >= 55) height = 2600 if (n_model >= 60) height = 2800 if (n_model >= 65) height = 3000 } # Convert to Plotly ggplotly(p, height = height) |> toWebGL() }) # ============================================================================ # Reactive: Payout Summary Table # ============================================================================ # Net Round Payout Summary output$dt_payout_summary <- DT::renderDT({ # Generate a new DT DT::datatable( # Data react_d_payout_summary(), # Other Options rownames = FALSE, extensions = "Buttons", options = list( dom = 'Bflrtip', # https://datatables.net/reference/option/dom buttons = list('csv', 'excel', 'copy', 'print'), # https://rstudio.github.io/DT/003-tabletools-buttons.html order = list(list(0, 'asc'), list(1, 'asc')), pageLength = 1000, lengthMenu = c(10, 50, 100, 500, 1000, 10000), columnDefs = list(list(className = 'dt-center', targets = "_all"))) ) |> # Reformat individual columns formatRound(columns = c("total_stake", "net_payout", "rate_of_return"), digits = 2) |> formatStyle(columns = c("round"), fontWeight = "bold") |> formatStyle(columns = c("resolved"), target = "row", backgroundColor = styleEqual(c(1,0), c("transparent", "#FFF8E1"))) |> formatStyle(columns = c("total_stake"), fontWeight = "bold", color = styleInterval(cuts = -1e-15, values = c("#D24141", "#2196F3"))) |> formatStyle(columns = c("net_payout"), fontWeight = "bold", color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) |> formatStyle(columns = c("rate_of_return"), fontWeight = "bold", color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) }) # Individual Model Payout Summary output$dt_model_payout_summary <- DT::renderDT({ # Generate a new DT DT::datatable( # Data react_d_model_payout_summary(), # Other Options rownames = FALSE, extensions = "Buttons", options = list( dom = 'Bflrtip', # https://datatables.net/reference/option/dom buttons = list('csv', 'excel', 'copy', 'print'), # https://rstudio.github.io/DT/003-tabletools-buttons.html order = list(list(0, 'asc'), list(1, 'asc')), pageLength = 100, lengthMenu = c(10, 50, 100, 500, 1000), columnDefs = list(list(className = 'dt-center', targets = "_all"))) ) |> # Reformat individual columns formatRound(columns = c("net_payout", "avg_payout", "avg_rate_of_return_percent", "sharpe_rate_of_return"), digits = 4) |> # formatStyle(columns = c("model"), fontWeight = "bold") |> formatStyle(columns = c("net_payout", "avg_payout", "avg_rate_of_return_percent", "sharpe_rate_of_return"), # fontWeight = "bold", color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) }) # Payout Sim (Model) output$dt_payout_sim_model <- DT::renderDT({ # Generate a new DT DT::datatable( # Data react_d_payout_sim_model(), # Other Options rownames = FALSE, extensions = "Buttons", options = list( dom = 'Bflrtip', # https://datatables.net/reference/option/dom buttons = list('csv', 'excel', 'copy', 'print'), # https://rstudio.github.io/DT/003-tabletools-buttons.html order = list(list(0, 'asc'), list(1, 'asc')), pageLength = 100, lengthMenu = c(10, 50, 100, 500, 1000), columnDefs = list(list(className = 'dt-center', targets = "_all"))) ) |> # Reformat individual columns formatRound(columns = c("sum_pay_1C0T", "sum_pay_2C0T", "sum_pay_2C1T", "sum_pay_1C3T", "sum_pay_05C2M", "shp_pay_05C2M", "shp_pay_1C0T", "shp_pay_2C0T", "shp_pay_2C1T", "shp_pay_1C3T"), digits = 2) |> formatStyle(columns = c("sum_pay_1C0T", "sum_pay_2C0T", "sum_pay_2C1T", "sum_pay_1C3T", "sum_pay_05C2M", "shp_pay_05C2M", "shp_pay_1C0T", "shp_pay_2C0T", "shp_pay_2C1T", "shp_pay_1C3T"), color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) |> formatStyle(columns = c("model", "sum_pay_05C2M", "shp_pay_05C2M" # "sum_pay_2C1T", "sum_pay_1C3T", # "shp_pay_2C1T", "shp_pay_1C3T" ), fontWeight = "bold") }) # Payout Sim (Overall) output$dt_payout_sim_overall <- DT::renderDT({ # Generate a new DT DT::datatable( # Data react_d_payout_sim_overall(), # Other Options rownames = FALSE, # extensions = "Buttons", options = list( dom = 't', # https://datatables.net/reference/option/dom # buttons = list('csv', 'excel', 'copy', 'print'), # https://rstudio.github.io/DT/003-tabletools-buttons.html # order = list(list(0, 'asc'), list(1, 'asc')), # pageLength = 10, # lengthMenu = c(10, 50, 100, 500, 1000), columnDefs = list(list(className = 'dt-center', targets = "_all"))) ) |> # Reformat individual columns formatRound(columns = c("sum_pay_1C0T", "sum_pay_2C0T", "sum_pay_2C1T", "sum_pay_1C3T", "sum_pay_05C2M", "shp_pay_05C2M", "shp_pay_1C0T", "shp_pay_2C0T", "shp_pay_2C1T", "shp_pay_1C3T"), digits = 2) |> formatStyle(columns = c("sum_pay_1C0T", "sum_pay_2C0T", "sum_pay_2C1T", "sum_pay_1C3T", "sum_pay_05C2M", "shp_pay_05C2M", "shp_pay_1C0T", "shp_pay_2C0T", "shp_pay_2C1T", "shp_pay_1C3T"), color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) |> formatStyle(columns = c("sum_pay_05C2M", "shp_pay_05C2M"), fontWeight = "bold") }) # Performance Summary output$dt_performance_summary <- DT::renderDT({ # Generate a new DT DT::datatable( # Data react_d_performance_summary(), # Other Options rownames = FALSE, extensions = "Buttons", options = list( dom = 'Bflrtip', # https://datatables.net/reference/option/dom buttons = list('csv', 'excel', 'copy', 'print'), # https://rstudio.github.io/DT/003-tabletools-buttons.html order = list(list(0, 'asc'), list(1, 'asc')), pageLength = 10, lengthMenu = c(10, 50, 100, 500, 1000), columnDefs = list(list(className = 'dt-center', targets = "_all"))) ) |> # Reformat individual columns formatRound(columns = c("avg_corrV2", "sharpe_corrV2", "avg_tc", "sharpe_tc", "avg_2C1T", "sharpe_2C1T" ), digits = 4) |> formatStyle(columns = c("avg_corrV2", "sharpe_corrV2", "avg_tc", "sharpe_tc", "avg_2C1T", "sharpe_2C1T" ), color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) }) # KPI Filter output$dt_kpi <- DT::renderDT({ # Generate a new DT DT::datatable( # Data react_d_kpi(), # Other Options rownames = FALSE, extensions = "Buttons", options = list( dom = 'Bflrtip', # https://datatables.net/reference/option/dom buttons = list('csv', 'excel', 'copy', 'print'), # https://rstudio.github.io/DT/003-tabletools-buttons.html order = list(list(0, 'asc'), list(1, 'asc')), pageLength = 5, lengthMenu = c(5, 10, 50, 100, 500, 1000), columnDefs = list(list(className = 'dt-center', targets = "_all"))) ) |> # Reformat individual columns formatRound(columns = c("KPI"), digits = 4) |> formatStyle(columns = c("KPI"), color = styleInterval(cuts = c(-1e-15, 1e-15), values = c("#D24141", "#D1D1D1", "#00A800"))) }) # ============================================================================ # Reactive: Model Performance Charts # ============================================================================ # Boxplot - TC Percentile output$plot_boxplot_tcp <- renderPlotly({ # Data d_filter <- react_d_filter() # Order by TC_PCT d_model_order <- with(d_filter, reorder(model, tc_pct, median)) d_filter$model_order <- factor(d_filter$model, levels = levels(d_model_order)) # ggplot2 p <- ggplot(d_filter, aes(x = model_order, y = tc_pct, group = model_order, color = model_order)) + geom_boxplot() + theme( panel.border = element_blank(), panel.background = element_rect(fill = 'transparent'), plot.background = element_rect(fill = 'transparent', color = NA), panel.grid.major.x = element_blank(), panel.grid.major.y = element_line(color = "grey", linewidth = 0.25), panel.grid.minor = element_blank(), strip.background = element_rect(fill = 'transparent'), strip.text = element_text(), strip.clip = "on", legend.position = "none" ) + scale_color_manual(values = gen_custom_palette(d_filter$model)) + xlab("Model") + ylab("TC Percentile") + scale_y_continuous(limits = c(0,100), breaks = breaks_pretty(4)) + coord_flip() # Dynamic height adjustment n_model <- length(unique(d_filter$model)) height <- 600 # default if (n_model > 10) height = 800 if (n_model > 15) height = 1000 if (n_model > 20) height = 1200 if (n_model > 25) height = 1400 if (n_model > 30) height = 1600 if (n_model > 35) height = 1800 if (n_model > 40) height = 2000 if (n_model > 45) height = 2200 if (n_model > 50) height = 2400 # Generate plotly ggplotly(p, height = height) }) # ============================================================================ # Reactive: Downloads # ============================================================================ output$download_raw <- downloadHandler( filename = "raw_data.csv", content = function(file) {fwrite(react_d_raw(), file, row.names = FALSE)} ) # ============================================================================ # Session Info # ============================================================================ output$session_info <- renderPrint({ sessionInfo() }) # ============================================================================ # Trick to keep session alive # https://tickets.dominodatalab.com/hc/en-us/articles/360015932932-Increasing-the-timeout-for-Shiny-Server # ============================================================================ output$keepAlive <- renderText({ req(input$count) # paste("keep alive ", input$count) " " }) } # ============================================================================== # App # ============================================================================== shinyApp(ui, server)