Search code examples
rdataframefiltercalculated-columnsstockquotes

Consecutive Positive or Negative calculation from data frame and filter results using R


I have the following dataset and looking to write a code that can help pull out which stocks have been positive or negative consecutively. The data would have first 3 column. last 2 columns are manually calculated in excel to depict expected results.

This is only sample, i would have data for 200+ stocks and few years of data with all stocks not trading every day.

In the end, i want to extract which stocks have say 3 or 4 or 5 consecutive positive or negative change for the day.

`    Stocks Date    Close Price Change for day  Positive/Negative Count
A   11/11/2020         11       
B   11/11/2020         50       
C   11/11/2020        164       
A   11/12/2020         19         8                 1
B   11/12/2020         62        12                 1
C   11/12/2020        125        -39               -1
A   11/13/2020          7        -12               -1
B   11/13/2020         63         1                 2
C   11/13/2020        165        40                 1
A   11/16/2020         17        10                 1
B   11/16/2020         70         7                 3
C   11/16/2020        170         5                 2
A   11/17/2020         24         7                 2
B   11/17/2020         52        -18               -1
C   11/17/2020        165         -5               -1
A   11/18/2020         31          7                3
B   11/18/2020         61          9                1
C   11/18/2020        157         -8               -2

Solution

  • The difficulty is to have a function that makes the cumulative sum, both positive and negative, resetting the count when the sign changes, and starting the count with the first value. I managed to make one, but it is not terribly efficient and will probably get slow on a bigger dataset. I suspect there is a way to do better, if only with a simple for loop in C or C++.

    library(tidyverse)
    
    
    df <- read.table(text="Stocks Date    Close_Price Change_for_day  Positive/Negative_Count
    A   11/11/2020         11       NA                 0
    B   11/11/2020         50       NA                 0
    C   11/11/2020        164       NA                 0
    A   11/12/2020         19         8                 1
    B   11/12/2020         62        12                 1
    C   11/12/2020        125        -39               -1
    A   11/13/2020          7        -12               -1
    B   11/13/2020         63         1                 2
    C   11/13/2020        165        40                 1
    A   11/16/2020         17        10                 1
    B   11/16/2020         70         7                 3
    C   11/16/2020        170         5                 2
    A   11/17/2020         24         7                 2
    B   11/17/2020         52        -18               -1
    C   11/17/2020        165         -5               -1
    A   11/18/2020         31          7                3
    B   11/18/2020         61          9                1
    C   11/18/2020        157         -8               -2",
               header = TRUE) %>%
      select(1:3) %>%
      as_tibble()
    
    
    # this formulation could be faster on data with longer stretches
    nb_days_cons2 <- function(x){
      n <- length(x)
      if(n < 2) x
      out <- integer(n)
      y <- rle(x)
      cur_pos <- 1
      for(i in seq_len(length(y$lengths))){
        out[(cur_pos):(cur_pos+y$lengths[i]-1)] <- cumsum(rep(y$values[i], y$lengths[i]))
        cur_pos <- cur_pos + y$lengths[i]
      }
      out
    }
    
    # this formulation was faster on some tests, and would be easier to rewrite in C
    nb_days_cons <- function(x){
      n <- length(x)
      if(n < 2) x
      out <- integer(n)
      out[1] <- x[1]
      for(i in 2:n){
        if(x[i] == x[i-1]){
          out[i] <- out[i-1] + x[i]
        } else{
          out[i] <- x[i]
        }
      }
      out
    }
    

    Once we have that function, the dplyr part is quite classic.

    df %>%
      group_by(Stocks) %>%
      arrange(Date) %>%   # make sure of order
      mutate(change = c(0, diff(Close_Price)),
             stretch_duration = nb_days_cons(sign(change))) %>%
      arrange(Stocks)
    #> # A tibble: 18 x 5
    #> # Groups:   Stocks [3]
    #>    Stocks Date       Close_Price change stretch_duration
    #>    <chr>  <chr>            <int>  <dbl>            <dbl>
    #>  1 A      11/11/2020          11      0                0
    #>  2 A      11/12/2020          19      8                1
    #>  3 A      11/13/2020           7    -12               -1
    #>  4 A      11/16/2020          17     10                1
    #>  5 A      11/17/2020          24      7                2
    #>  6 A      11/18/2020          31      7                3
    #>  7 B      11/11/2020          50      0                0
    #>  8 B      11/12/2020          62     12                1
    #>  9 B      11/13/2020          63      1                2
    #> 10 B      11/16/2020          70      7                3
    #> 11 B      11/17/2020          52    -18               -1
    #> 12 B      11/18/2020          61      9                1
    #> 13 C      11/11/2020         164      0                0
    #> 14 C      11/12/2020         125    -39               -1
    #> 15 C      11/13/2020         165     40                1
    #> 16 C      11/16/2020         170      5                2
    #> 17 C      11/17/2020         165     -5               -1
    #> 18 C      11/18/2020         157     -8               -2
    Created on 2020-11-19 by the reprex package (v0.3.0)
    

    Of course, the final arrange() is just for easy visualization, and you can remove the columns you don't need anymore with select().