Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
115 changes: 110 additions & 5 deletions .github/workflows/remote-install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
paths:
- 'configure'
- 'configure.win'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -21,7 +22,9 @@ concurrency:
permissions: read-all

jobs:
R-CMD-check:
## Vanilla remote install - verifies pak::pak("serkor1/ta-lib-R")
## works on a clean runner with no compiler-wrapping in ~/.R/Makevars.
Remote-Install:
runs-on: ${{ matrix.config.os }}

name: ${{ matrix.config.os }} (${{ matrix.config.r }})
Expand All @@ -31,19 +34,19 @@ jobs:
matrix:
config:
## macOS
- {os: macos-latest, r: 'devel', http-user-agent: 'release'}
- {os: macos-latest, r: 'release'}
- {os: macos-latest, r: 'oldrel-1'}
- {os: macos-latest, r: 'oldrel-2'}

## windows
- {os: windows-latest, r: 'devel', http-user-agent: 'release'}
- {os: windows-latest, r: 'release'}
- {os: windows-latest, r: 'oldrel-1'}

- {os: windows-latest, r: 'oldrel-2'}

# Ubuntu
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-latest, r: 'oldrel-1'}
- {os: ubuntu-latest, r: 'oldrel-2'}

env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -72,3 +75,105 @@ jobs:
- name: Calculate an indicator
run: talib::bollinger_bands(talib::BTC)
shell: Rscript {0}

## Remote install with ccache configured in ~/.R/Makevars - regression
## guard for https://github.com/serkor1/ta-lib-R/issues/57, where installs
## with `CC = ccache gcc` in Makevars hit
## "/usr/bin/ccache: invalid option -- 'D'" because the configure script
## passed `CMAKE_C_COMPILER=ccache` and CMake then invoked ccache with
## raw `-D` definitions.
Remote-Install-ccache:
runs-on: ${{ matrix.config.os }}

name: ${{ matrix.config.os }} (${{ matrix.config.r }}, ccache)

strategy:
fail-fast: false
matrix:
config:
## macOS
- {os: macos-latest, r: 'release'}
- {os: macos-latest, r: 'oldrel-1'}
- {os: macos-latest, r: 'oldrel-2'}

## windows
- {os: windows-latest, r: 'release'}
- {os: windows-latest, r: 'oldrel-1'}
- {os: windows-latest, r: 'oldrel-2'}

# Ubuntu
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-latest, r: 'oldrel-1'}
- {os: ubuntu-latest, r: 'oldrel-2'}

env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
R_KEEP_PKG_SOURCE: yes

steps:
- uses: actions/checkout@v4

- uses: r-lib/actions/setup-pandoc@v2

- uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
http-user-agent: ${{ matrix.config.http-user-agent }}
use-public-rspm: true

- name: Install ccache (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y ccache
shell: bash

- name: Install ccache (macOS)
if: runner.os == 'macOS'
run: brew install ccache
shell: bash

- name: Install ccache (Windows)
if: runner.os == 'Windows'
run: choco install ccache -y --no-progress
shell: pwsh

- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::pak any::plotly any::devtools any::roxygen2
needs: check

- name: Configure ~/.R/Makevars to use ccache
run: |
dir <- path.expand(file.path("~", ".R"))
dir.create(dir, recursive = TRUE, showWarnings = FALSE)
name <- if (.Platform$OS.type == "windows") "Makevars.win" else "Makevars"
if (Sys.info()[["sysname"]] == "Darwin") {
cc <- "clang"; cxx <- "clang++"
} else {
cc <- "gcc"; cxx <- "g++"
}
path <- file.path(dir, name)
writeLines(
c(paste0("CC=ccache ", cc),
paste0("CXX=ccache ", cxx)),
path
)
cat("--- ", path, " ---\n", sep = "")
cat(readLines(path), sep = "\n"); cat("\n")
shell: Rscript {0}

- name: Reset ccache statistics
run: ccache --zero-stats
shell: bash

- name: Install {talib}
run: pak::pak("serkor1/ta-lib-R")
shell: Rscript {0}

- name: Show ccache statistics
if: always()
run: ccache -s
shell: bash

- name: Calculate an indicator
run: talib::bollinger_bands(talib::BTC)
shell: Rscript {0}
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: talib
Title: Interface to 'TA-Lib' for Technical Analysis and Candlestick Patterns
Version: 0.9-2
Version: 0.9-3
Authors@R: c(
person(
given = "Serkan",
Expand Down
21 changes: 11 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,21 @@ pkgdown-preview: ## Preview {pkgdown} documetation
@Rscript -e "pkgdown::preview_site()"

bench: ## Run benchmark(s)
@echo -e "Running benchmark..."
@echo -e "Running full benchmark suite (overhead + TTR comparison)..."
@echo -e ""
@Rscript ./benchmark/benchmark-overhead.R
@Rscript ./benchmark/run-all.R
@echo -e ""
@echo -e "Benchmark information:"
@echo -e " -baseline: no R overhead"
@echo -e " -data.frame: data.frame methods"
@echo -e " -baseline: matrix methods"
@echo -e "Rendering benchmark/README.Rmd..."
@cd benchmark && Rscript -e "rmarkdown::render('README.Rmd', output_format = rmarkdown::github_document(html_preview = FALSE), clean = TRUE)"


n ?= 1e6
bench-data: ## Generate data for benchmark(s)
@Rscript ./benchmark/benchmark-data.R $(n)
bench-overhead: ## Run only the overhead benchmark
@Rscript ./benchmark/benchmark-overhead.R

bench-ttr: ## Run only the talib-vs-TTR benchmark
@Rscript ./benchmark/benchmark-ttr.R

bench-plots: ## Regenerate plots from existing RDS results
@Rscript ./benchmark/benchmark-plots.R

validate: ## Validate R output against TA-Lib core
@PKG_CFLAGS="-Isrc/ta-lib/local/include -Isrc/ta-lib/local/include/ta-lib" \
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,7 @@ export(kicking_baby_length)
export(ladder_bottom)
export(long_legged_doji)
export(long_line)
export(lookback)
export(marubozu)
export(mat_hold)
export(matching_low)
Expand Down
19 changes: 19 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# version 0.9-3

## improvements

* A new function for pre-calculating the lookback-period has been implemented. It can be used as follows:

```R
talib::lookback(
FUN = talib::SMA,
n = 10,
x = talib::BTC
)
```

The function returns the minimum required lookback for calculating the indicator.
Its use-case is customized control-flows for downstream wrappers and/or packages that declares dependency on {talib}.

## bug-fixes

# version 0.9-2

## improvements
Expand Down
126 changes: 126 additions & 0 deletions R/lookback.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#' @export
#' @family Utility
#'
#' Calculate lookback period
#'
#' @description
#' The function calculates the lookback period for a given
#' indicator.
#' Its primarily meant as a helper function for downstream packages
#' that wants to use a customized control-flow.
#'
#' @examples
#' ## calculate the lookback
#' ## for the bollinger bands
#' talib::lookback(
#' talib::bollinger_bands,
#' n = 20,
#' x = talib::BTC
#' )
#'
#' @param FUN A [call] or [function].
#' @param ... Additional parameters passed into the indicator function. See examples for more details.
#'
#' @concept finance
#' @concept technical analysis
#' @concept trading
#' @concept algorithmic trading
#'
#'
#' @author Serkan Korkmaz
#'
#' @returns
#' The minimum lookback required to calculate the indicator.
#' If the indicator specification and input data are invalid the function returns [NA], otherwise it returns an [integer] of [length] 1.
lookback <- function(
FUN,
...
) {
## store {talib} as a namespace
## so the function can be found
## (*_lookback is not exported)
ns <- getNamespace(
"talib"
)

## extract the function call
## as-is
FUN <- substitute(
FUN
)

## important distinction with substitute:
## pkg::foo() -> call
## foo -> function
if (is.call(FUN)) {
FUN <- FUN[[length(FUN)]]
}

## all exported indicators has
## a _lookback post-fix which handles
## the lookback calculation
FUN <- paste0(
as.character(FUN),
"_lookback"
)

if (!exists(FUN, envir = ns, mode = "function", inherits = FALSE)) {
## strip FUN to get
## the basename of the passed
## function
FUN <- gsub(
pattern = "_lookback",
replacement = "",
x = FUN
)

## stop the function with
## a hard error.
## TODO: Consider the case for
## custom indicators, wrapper functions
## or exported functions that does not have
## a lookback-calculation.
stop(
"No indicator named `",
FUN,
"` was found.",
call. = FALSE
)
}

FUN <- get(
FUN,
envir = ns,
mode = "function",
inherits = FALSE
)

## upstream returns -1
## for invalid input data
## relative to the indicator;
## SMA, for example, requires at
## minimum two data points to calculate
## given n = 2 - and three, if n = 3.
##
## if the indicator and input are not
## satisfying this constraint upstream
## returns -1, ie. not applicable.
minimum_lookback <- do.call(
FUN,
args = list(...)
)

if (minimum_lookback == -1) {
return(NA)
}

## volume, for example, returns
## a lookback of 0 if calculated without
## moving averages - wrap the lookback
## in max() as a safety precaution
max(
minimum_lookback,
1,
na.rm = TRUE
)
}
32 changes: 32 additions & 0 deletions R/ta_ACCBANDS.R
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,38 @@ acceleration_bands.matrix <- function(
)
}

#' @usage NULL
ACCBANDS_lookback <- acceleration_bands_lookback <- function(
x,
cols,
n = 20,
...
) {
## validate 'cols'-argument
## if explicitly passed
if (!missing(cols)) {
assert_formula(cols)
}

## construct series
## from input
constructed_series <- series(
x = cols,
default_formula = ~ high + low + close,
data = x,
...
)

.Call(
C_impl_ta_ACCBANDS_lookback,
## splice:lookback:start
constructed_series[[1]],
constructed_series[[2]],
constructed_series[[3]],
as.integer(n)
## splice:lookback:end
)
}

#' @usage NULL
#' @aliases acceleration_bands
Expand Down
Loading
Loading