Skip to contents

The strategy pulse context is the object passed to every function(ctx, params) strategy at one decision time. It exposes only data that is known at that pulse: current bars, registered current feature values, current simulated positions, cash, equity, and helpers for building target vectors.

Details

Common accessors:

  • ctx$ts_utc: current decision timestamp.

  • ctx$universe: character vector on the current decision axis. In availability-aware runs this can include held nonmembers that remain necessary for valuation and disposition.

  • ctx$members: the current allocation population. In a dense run it is ctx$universe.

  • ctx$vec$member, ctx$vec$target_restricted, ctx$vec$target_restriction_reason and ctx$vec$admissible: the eligibility planes, aligned to ctx$universe, in every run. An instrument is admissible when it is a member and not target-restricted. In a dense run every instrument is a member, none is restricted, every reason is "" and every instrument is admissible, so a rule masked with ctx$vec$admissible works unchanged in both kinds of run. The context helpers, including ledgr_passed_warmup(ctx, values), consider admissible instruments only, and fail when an availability context lacks a well-formed ctx$vec$admissible plane.

  • ctx$idx(id): 1-based universe position for one instrument.

  • ctx$open(id), ctx$high(id), ctx$low(id), ctx$close(id), ctx$volume(id): current bar values for one instrument.

  • ctx$vec$open, ctx$vec$high, ctx$vec$low, ctx$vec$close, ctx$vec$volume: current bar values for the full universe, aligned to ctx$universe.

  • ctx$bar(id): the current OHLCV row for one instrument.

  • ctx$feature(id, feature_id): one feature value for one instrument, addressed by an active alias or concrete engine feature ID.

  • ctx$vec$feature(feature_id): one active alias or concrete feature ID across the full universe, aligned to ctx$universe.

  • ctx$features(id, feature_map): mapped feature values for one instrument, returned as a named numeric vector keyed by feature-map aliases.

  • ctx$position(id): current simulated position for one instrument.

  • ctx$vec$position: current simulated positions for the full universe, aligned to ctx$universe.

  • ctx$cash and ctx$equity: current simulated portfolio state.

  • ctx$state_prev: the state_update the strategy returned at the previous pulse.

  • ctx$flat(): full named target vector initialized to zero.

  • ctx$hold(): full named target vector initialized to current positions.

  • ctx$tradable(): convenience subset of current members that are admissible and priced in an availability-aware run; in a dense run it returns instruments with a finite positive close. A permissible stale mark can satisfy the predicate without supplying a current sizing close, and the result does not promise execution at the next open.

Singular reads resolve an active feature-map alias first, then a concrete engine ID from ledgr_feature_id(). ctx$features() returns one instrument's mapped values named by alias. Unknown names fail loudly and list active aliases separately from concrete IDs; warmup for a known feature is represented by NA_real_. No plural whole-universe accessor is added: read each required alias once with ctx$vec$feature().

For universes of at least 100 instruments, reading one name through ctx$feature() once per instrument emits one ledgr_scalar_accessor_loop warning per run and names ctx$vec$feature(feature_id) as the whole-universe alternative. An active-map ctx$features(id) loop warns at the same axis-sized threshold and names the active aliases to read through that plane. Explicit-map bundle reads remain unrecorded because the plane does not accept an explicit map.

ctx$idx(id) fails loudly for unknown instruments by default. Use ctx$idx(id, missing = "na") only when an explicit NA_integer_ sentinel is part of the strategy logic.

ctx$position(id) is the scalar pulse-start state read and ctx$vec$position is its unnamed decision-axis-aligned plane. Use ctx$idx(id) to cross between them. ctx$hold() constructs named portfolio intent from that state; editing its result does not mutate either state read.

A strategy that returns list(targets = ..., state_update = ...) receives that state_update as ctx$state_prev at the next pulse; returning a bare target keeps the previous state. State must be JSON-serializable plain data. In a dense run ctx$state_prev is NULL on the first pulse. In an availability-aware run it is a list from the first pulse and carries asset_state, a list keyed by the instrument IDs in ctx$universe: entries for instruments that leave the axis are dropped, returning instruments start from list(), and a returned entry for an ID outside the axis fails with ledgr_invalid_strategy_state.

The structured result may also include intent_reason, either NULL or one non-empty character scalar. When supplied, ledgr records the value once at portfolio level for that invoked pulse in ledgr_results(run, "diagnostics"); availability-aware ledgr_run_explain() joins it to the existing instrument explanation. Prefer short snake-case values. This optional note does not change strategy state, targets, execution, accounting or identity, and is not a general factor or prediction store.

Strategies should return full named numeric target vectors. The helpers ctx$flat() and ctx$hold() are the safest starting points because they already have one entry for every instrument in ctx$universe. In availability-aware runs, ctx$universe is the decision axis and ctx$members is the current allocation population; held nonmembers may remain on the former without entering the latter. In a dense run the two are the same. Use ctx$tradable() as the explicit convenience predicate for admissible and priced members. A stale permissible mark can satisfy that predicate while still failing current-close sizing, so it guarantees neither a sizing close nor execution at the next open.

Complete Public Surface

The table below is authoritative for callback-visible, non-private members. Daxis means ctx$universe; every plane is positional and unnamed. Cross a plane with IDs through ctx$idx(). Dot-prefixed engine state is private and deliberately absent.


scope | member | applies | shape | axis | units | missingness | counterpart
top | run_id | all | character_scalar | none | identifier | never | none
top | ts_utc | all | character_scalar | none | UTC_time | never | none
top | universe | all | character_vector | Daxis | instrument_id | empty_allowed | vec.id
top | bars | all | data_frame | current_Daxis_rows | market_data | rows_may_be_absent | bar
top | feature_table | all | data_frame | current_Daxis_rows | feature_value | warmup_NA_or_schema_only | feature
top | cash | all | numeric_scalar | portfolio | currency | never | none
top | equity | all | numeric_scalar | portfolio | currency | never | none
top | seed | all | nullable_scalar | none | integer_seed | NULL_allowed | none
top | pulse_seed | all | nullable_scalar | none | integer_seed | NULL_allowed | none
top | state_prev | all | nullable_list | portfolio_and_Daxis | strategy_state | NULL_on_first_dense_pulse | none
top | availability_active | availability | logical_scalar | none | flag | never | none
top | members | all | character_vector | Daxis_subset | instrument_id | empty_allowed | vec.member
top | bar | all | function | instrument_id | OHLCV_row | absent_row_errors | bars
top | open | all | function | instrument_id | price | NA_allowed | vec.open
top | high | all | function | instrument_id | price | NA_allowed | vec.high
top | low | all | function | instrument_id | price | NA_allowed | vec.low
top | close | all | function | instrument_id | price | NA_allowed | vec.close
top | volume | all | function | instrument_id | source_units | NA_allowed | vec.volume
top | position | all | function | instrument_id | quantity | never | vec.position
top | idx | all | function | instrument_id | Daxis_index | optional_NA | vec.id
top | flat | all | function | Daxis | target_quantity | never | none
top | hold | all | function | Daxis | target_quantity | never | vec.position
top | tradable | all | function | Daxis_subset | instrument_id | empty_allowed | mode_specific_planes
top | features_wide | all | data_frame | current_Daxis_rows | feature_value | warmup_NA | features
top | feature | all | function | instrument_id | feature_value | warmup_NA | vec.feature
top | features | all | function | instrument_id | feature_value | warmup_NA | vec.feature
top | vec | all | list | Daxis | mixed | plane_specific | scalar_counterparts
plane | id | all | character_vector | Daxis | instrument_id | never | universe
plane | open | all | numeric_vector | Daxis | price | NA_allowed | open
plane | high | all | numeric_vector | Daxis | price | NA_allowed | high
plane | low | all | numeric_vector | Daxis | price | NA_allowed | low
plane | close | all | numeric_vector | Daxis | price | NA_allowed | close
plane | volume | all | numeric_vector | Daxis | source_units | NA_allowed | volume
plane | position | all | numeric_vector | Daxis | quantity | never | position
plane | feature | all | function | Daxis | feature_value | warmup_NA | feature
plane | member | all | logical_vector | Daxis | flag | never | members
plane | held | availability | logical_vector | Daxis | flag | never | none
plane | target_restricted | all | logical_vector | Daxis | flag | never | none
plane | target_restriction_reason | all | character_vector | Daxis | reason_token | empty_string_allowed | none
plane | admissible | all | logical_vector | Daxis | flag | never | tradable
plane | priced | availability | logical_vector | Daxis | flag | never | tradable
plane | mark_age | availability | integer_vector | Daxis | sessions | NA_when_unpriced | none
plane | risk_mark | availability | numeric_vector | Daxis | price | NA_when_unpriced | none
plane | mark_source | availability | character_vector | Daxis | source_token | empty_when_unpriced | none

Feature Object Compatibility

ledgr_experiment(features = ...) accepts static indicators, lists of indicators, named lists, and feature maps as the canonical public feature shapes. Compatibility feature factories of the form function(params) remain available for old flat-grid and advanced exact-ID workflows, but new parameterized feature sweeps should use active aliases with ledgr_feature_map(). Pulse inspection helpers accept static feature shapes. Inside the strategy context, ctx$feature() reads one engine feature ID and ctx$features() requires a ledgr_feature_map. Lower-level or legacy helpers may be narrower; the experiment-first workflow is the canonical public path for feature maps.

Articles

Strategy authoring: vignette("strategy-development", package = "ledgr") system.file("doc", "strategy-development.html", package = "ledgr")

Indicator IDs and feature maps: vignette("indicators", package = "ledgr") system.file("doc", "indicators.html", package = "ledgr")

Examples

features <- ledgr_feature_map(
  ret_5 = ledgr_ind_returns(5),
  sma_10 = ledgr_ind_sma(10)
)

strategy <- function(ctx, params) {
  targets <- ctx$flat()
  for (id in ctx$universe) {
    x <- ctx$features(id, features)
    if (ledgr_passed_warmup(x) && x[["ret_5"]] > params$min_return) {
      targets[id] <- params$qty
    }
  }
  targets
}