/// Mark a function as a cached technical indicator.
///
/// Indicator functions compute technical-analysis values from price data.
/// This annotation registers the function in the indicator registry and enables
/// memoization across repeated calls with the same arguments.
///
/// @note The annotation contributes lifecycle hooks, but the annotated
/// function remains the source of truth for its own API signature and docs.
/// @see std::finance::annotations::warmup::@warmup
/// @example
/// @indicator
/// @warmup(14)
/// pub fn rsi(data: Column<number>, period: number) -> number {
/// let gains = data.diff().map(d => d > 0 ? d : 0)
/// let losses = data.diff().map(d => d < 0 ? abs(d) : 0)
/// let avg_gain = gains.rolling(period).mean()
/// let avg_loss = losses.rolling(period).mean()
/// let rs = avg_gain / avg_loss
/// return 100 - (100 / (1 + rs))
/// }
annotation indicator() {
// Called when a function with @indicator is defined
// self = the annotated function
on_define(ctx) {
// Register in the indicators registry
ctx.get("registry").set(self.name, self)
}
// Called before each invocation - check cache
// self = the annotated function
before(args, ctx) {
// Generate cache key from function name and arguments
let key = self.name + ":" + args.toString()
// Check if we have a cached result
let cached = ctx.get("cache").get(key)
if cached != None {
// Return cached value — wrapper will use self instead of calling impl
return cached
}
// Return None to continue with normal execution
None
}
// Called after each invocation - cache result
// self = the annotated function
after(args, result, ctx) {
// Generate cache key
let key = self.name + ":" + args.toString()
// Return state with cache update
result
}
// Return static metadata
metadata() {
return {
is_indicator: true,
cacheable: true,
pure: true
}
}
}