strategyr is an execution-oriented R package for modular strategy workflows. It is designed to transform already-available market features, portfolio state, and dynamic quantitative analysis into actionable signals, target positions, portfolio adjustments, and order intents.
The package emphasizes path-dependent backtesting so strategy logic is evaluated under evolving account state, execution assumptions, and market conditions rather than through purely static signal research.
Compared with investlabr, which is oriented toward exploratory research and communication, strategyr is intended for disciplined strategy logic and timely investment decision support.
# devtools::install_github("OliverLDS/strategyr")
install.packages("strategyr", repos = c('https://oliverlds.r-universe.dev', 'https://cloud.r-project.org'))
library(data.table)
library(strategyr)
DT <- data.table(
datetime = as.POSIXct("2020-01-01", tz = "UTC") + 86400 * 0:249
)
DT[, close := 100 + cumsum(sin(seq_len(.N) / 8) + 0.15)]
DT[, open := shift(close, fill = close[1])]
DT[, high := pmax(open, close) + 1]
DT[, low := pmin(open, close) - 1]
tgt_pos <- strat_donchian_turtle_tgt_pos(
DT,
entry_n = 30L,
exit_n = 10L,
target_size = 0.95
)
eq <- backtest_rcpp(
timestamp = as.numeric(DT$datetime),
open = DT$open,
high = DT$high,
low = DT$low,
close = DT$close,
tgt_pos = tgt_pos,
pos_strat = rep(307L, nrow(DT)),
tol_pos = rep(0.05, nrow(DT)),
strat = 307L,
asset = 8001L,
ctr_step = 0.01,
lev = 1,
fee_rt = 0.0005,
rec = TRUE
)
calc_strategy_performance_summary(
eq,
tgt_pos = tgt_pos,
recorder = attr(eq, "recorder"),
fee_rt = 0.0005
)
state <- list(
ctr_size = 1.0,
ctr_step = 1.0,
lev = 10.0,
last_px = 100.0,
ctr_unit = 0.0,
avg_price = NaN,
cash = 10000.0,
pos_dir = 0L
)
plan <- strat_donchian_turtle_action_plan(
DT,
state,
entry_n = 30L,
exit_n = 10L,
target_size = 0.95,
strat_id = 307L
)
walk_res <- mine_strategy_walk_forward(
DT,
strategy_fun = strat_ema_triple_trend_tgt_pos,
param_grid = data.table::CJ(
fast = c(10L, 20L),
mid = c(30L, 50L),
slow = c(80L, 120L),
target_size = 0.95
)[fast < mid & mid < slow],
train_years = 1,
test_years = 0.5,
step_years = 0.5,
n_best = 2L,
min_train_rows = 100L,
min_test_rows = 50L,
warmup_days = 120L,
strat_id = 106L,
ctr_step = 0.01,
lev = 1,
fee_rt = 0.0005
)
summarize_walk_forward_results(walk_res, group_cols = c("fast", "mid", "slow"))
filter_walk_forward_results(
walk_res,
group_cols = c("fast", "mid", "slow"),
min_windows = 1L,
min_positive_return_rate = 0.5,
max_avg_score_decay = 10
)
panel <- CJ(
date = as.Date("2020-01-01") + 0:9,
asset = c("AAA", "BBB", "CCC")
)
panel[, score := fifelse(asset == "AAA", 0.8, fifelse(asset == "BBB", 0.4, 0.2)) + as.numeric(date - min(date)) / 100]
panel[, open := 100 + match(asset, c("AAA", "BBB", "CCC")) * 5 + as.numeric(date - min(date))]
panel[, close := open * (1 + score / 100)]
panel[, target_weight := strat_cross_sectional_rank_allocator_tgt_pos(
panel,
date_col = "date",
asset_col = "asset",
signal_col = "score",
long_n = 2L,
short_n = 0L,
gross_exposure = 1.0
)]
portfolio_bt <- backtest_portfolio_weights(
panel,
initial_equity = 100000,
fee_rt = 0.0005
)
portfolio_bt$equity[, .(date, equity, gross_exposure, turnover, fee_paid)]
Daily allocation generators accept a long OHLC panel and return one target weight per asset-date. A target on date t was formed from the preceding completed bar and is therefore executable at date t open. Weights are long-only, finite, capped, and leave any unused exposure as cash.
allocation_panel <- CJ(
date = as.Date("2020-01-01") + 0:79,
asset = c("AAA", "BBB", "CCC")
)
allocation_panel[, asset_idx := match(asset, c("AAA", "BBB", "CCC"))]
allocation_panel[, close := 100 + asset_idx * 10 + as.integer(date - min(date)) * c(0.4, 0.2, -0.1)[asset_idx]]
allocation_panel[, open := close - 0.1]
allocation_panel[, high := close + 0.2]
allocation_panel[, low := open - 0.2]
allocation_panel[, asset_idx := NULL]
targets <- strat_cross_asset_trend_allocation_target_weights(
allocation_panel,
trend_n = 20L,
vol_n = 10L,
min_obs = 20L,
rebalance_n = 5L,
gross_exposure = 1.0,
weight_cap = 0.5
)
portfolio_result <- strat_portfolio_daily_backtest(
allocation_panel,
targets,
initial_cash = 100000,
fee_rt = 0.0005
)
portfolio_result$equity
portfolio_result$weights
bond_dt <- data.table(
par = rep(100, 5),
c_rate = rep(0.05, 5),
maturity = seq(2, 6),
freq = rep(2, 5),
ytm = c(0.045, 0.044, 0.043, 0.042, 0.041)
)
carry_tgt <- strat_bond_carry_roll_tgt_pos(
bond_dt,
long_threshold = 0,
short_threshold = -0.02,
target_size = 0.5
)
bond_state <- calc_bond_risk_state(
par = 100,
c_rate = 0.06,
maturity = 3,
freq = 2,
ytm = 0.05,
accrual_frac = 0.25
)
dv01_plan <- plan_duration_neutral_adjustment(
current_dv01 = bond_state$dv01 * 1000,
target_dv01 = 0,
hedge_dv01 = -125
)
option_chain <- CJ(
date = as.Date("2020-01-01") + 0:79,
time_to_expiry = c(30, 60) / 365,
type = c("put", "call")
)
option_chain[, option_log_forward_moneyness := fifelse(type == "put", -0.1, 0.1)]
option_chain[, close := 100 + as.numeric(date - min(date)) * 0.1]
option_chain[, iv := 0.2 + fifelse(type == "put", 0.03, -0.01) + sin(seq_len(.N) / 20) / 100]
iv_tgt <- strat_iv_directional_overlay_tgt_pos(
option_chain,
trend_n = 20L,
skew_long_threshold = 0.02,
skew_short_threshold = -0.02,
overlay_mode = "confirm"
)
option_state <- calc_option_risk_state(
S = 100,
K = 100,
time_to_expiry = 30 / 365,
r = 0.04,
sigma = 0.25,
type = "call"
)
delta_plan <- plan_delta_neutral_adjustment(
current_delta = option_state$delta * 100,
target_delta = 0,
hedge_delta = -50
)
strategy_public_definition("donchian_turtle")
strategy_monitor_definition("donchian_turtle")
strategy_public_definition() is the canonical public-safe source for Vox
strategy descriptions and effective default parameters. Parameter values are
finite numbers or NULL; NULL explicitly marks an unbounded setting that is
disabled by default so definitions remain JSON-safe.
strategy_monitor_definition() is the canonical public-safe source for
Strategy Monitor family and regime metadata. Current Vox monitor ids are
buy_hold, ema_cross_adx, ema_cross_slope_confirm, rsi_revert,
vol_target, donchian_turtle, bollinger_revert, and regime_switch.
The durable buy_hold id is displayed as Constant Exposure: it maintains a
target and rebalances only outside the configured tolerance. Buy-and-Hold is
reserved for passive benchmark calculations that make an initial purchase and
allow exposure to drift.
Oliver Zhou oliver.yxzhou@gmail.com
MIT License