Lifecycle Components
Lifecycle components let you move strategy lifecycle code out of a large inline Strategy implementation and into smaller AQE components. You can still write all of this logic directly inside on_start, init, on_teardown, and universe; the component model exists to make projects easier to segment, test, reuse, and expose in AQS.
Use lifecycle components when a block of logic has its own responsibility, configuration, failure policy, or reuse value.
Strategy lifecycle
Section titled “Strategy lifecycle”AQE owns a StrategyState and runs the strategy in a predictable order:
on_start(ctx)for one-time startup.universe(ctx)for static or inline symbol selection.- registered universe models for modular symbol selection.
- asset metadata loading for the final merged universe.
init(ctx, asset)once for each tradable asset.- per-bar history updates, indicators, alpha models, strategy callbacks, and insight pipes.
on_teardown(ctx)for shutdown cleanup.
Lifecycle logic blocks wrap the generated or inline strategy lifecycle methods. Universe models contribute symbols to the final universe.
Lifecycle logic traits
Section titled “Lifecycle logic traits”AQE exposes one trait per lifecycle phase:
pub trait OnStartLogic { fn version(&self) -> &str; fn run(&mut self, ctx: &mut dyn StrategyContext) -> LifecycleResult;}
pub trait OnInitLogic { fn version(&self) -> &str; fn run(&mut self, ctx: &mut dyn StrategyContext, asset: &Asset) -> LifecycleResult;}
pub trait OnTeardownLogic { fn version(&self) -> &str; fn run(&mut self, ctx: &mut dyn StrategyContext) -> LifecycleResult;}Each run method returns LifecycleResult, which records success, an optional message, and component metadata. A failed component with can_fail(false) stops the run; a failed component with can_fail(true) logs the failure and lets the run continue.
Default failure policy:
OnStartLogic: strict by default.OnInitLogic: strict by default.OnTeardownLogic: tolerant by default, because cleanup should continue where possible.
Timing
Section titled “Timing”Lifecycle wrappers use LifecycleTiming to choose where they run around the strategy method body:
LifecycleTiming::BeforeGeneratedLifecycleTiming::AfterGeneratedIn generated AQS projects, “generated” means the method body generated from the graph. In hand-written AQE projects, it is the inline body you wrote in the Strategy implementation.
Use BeforeGenerated for validation, environment setup, state seeding, and dependencies that the strategy body expects. Use AfterGenerated for follow-up work, registration checks, and metadata that depends on the strategy body having run.
Registering lifecycle logic
Section titled “Registering lifecycle logic”Direct AQE projects register lifecycle logic on StrategyState before starting the run. The engine calls the components automatically; user strategy code should not call lifecycle run helpers manually.
let mut state = StrategyState::new( "My Strategy".to_string(), "1.0".to_string(), MyStrategy {}, broker, StrategyMode::Backtest, timeframe,);
state.add_on_start_logic( OnStartLogicBuilder::new(Box::new(SeedRuntimeState::new())) .timing(LifecycleTiming::BeforeGenerated) .can_fail(false) .build(),);
state.add_on_init_logic( OnInitLogicBuilder::new(Box::new(InitAssetState::new())) .timing(LifecycleTiming::AfterGenerated) .can_fail(false) .build(),);
state.add_on_teardown_logic( OnTeardownLogicBuilder::new(Box::new(FlushRuntimeLogs::new())) .timing(LifecycleTiming::AfterGenerated) .can_fail(true) .build(),);Example on-start component
Section titled “Example on-start component”use aq_engine::core::lifecycle::{LifecycleResult, OnStartLogic};use aq_engine::core::strategy::StrategyContext;use serde_json::json;
pub struct SeedRuntimeState;
impl SeedRuntimeState { pub fn new() -> Self { Self }}
impl OnStartLogic for SeedRuntimeState { fn version(&self) -> &str { "1.0" }
fn run(&mut self, ctx: &mut dyn StrategyContext) -> LifecycleResult { ctx.variables().insert( self.name().to_string(), json!({ "started": true, "symbols_seen": 0 }), );
LifecycleResult::passed(self.name().to_string()) }}Universe models
Section titled “Universe models”A universe model is a modular symbol selector. It returns symbols through UniverseResult:
pub trait UniverseModel { fn version(&self) -> &str; fn run(&mut self, ctx: &mut dyn StrategyContext) -> UniverseResult;}AQE merges the symbols returned by Strategy::universe(ctx) and every registered universe model. The final universe is a de-duplicated union. That means a static universe can remain inline while one or more universe models add symbols from watchlists, filters, account rules, or date-aware selection logic.
state.add_universe_model( UniverseModelBuilder::new(Box::new(MyUniverseModel::new())) .can_fail(false) .build(),);Example:
use aq_engine::core::strategy::StrategyContext;use aq_engine::core::universe::{UniverseModel, UniverseResult};use std::collections::HashSet;
pub struct CryptoMajors;
impl CryptoMajors { pub fn new() -> Self { Self }}
impl UniverseModel for CryptoMajors { fn version(&self) -> &str { "1.0" }
fn run(&mut self, _ctx: &mut dyn StrategyContext) -> UniverseResult { let symbols = ["BTCUSD", "ETHUSD"] .into_iter() .map(str::to_string) .collect::<HashSet<_>>();
UniverseResult::passed(symbols, self.name().to_string()) }}Inline or modular
Section titled “Inline or modular”Inline lifecycle code is still valid:
- put simple startup registration in
Strategy::on_start; - put simple per-asset setup in
Strategy::init; - return a fixed symbol set from
Strategy::universe; - place one-off cleanup in
Strategy::on_teardown.
Prefer lifecycle components and universe models when the code has a clear module boundary, needs to be visible in AQS, has its own constructor configuration, or should be reused across strategies.
Runtime state
Section titled “Runtime state”Lifecycle components receive the same StrategyContext as strategies, alpha models, and insight pipes. Use ctx.variables() for shared runtime state that should not become AQS configuration.
Good uses for ctx.variables():
- alpha model state seeded during startup;
- per-symbol metadata initialized in
OnInitLogic; - cached universe metadata;
- counters, timestamps, and runtime-only flags;
- diagnostics needed by teardown or result review.
Keep user-tuned inputs in constructor arguments. Keep mutable runtime bookkeeping in ctx.variables() so the AQS property panel stays focused on real configuration.
We use essential cookies and storage for sign-in, account security, colour theme preferences, this notice, and required PostHog internal usage, session quality, and reliability metrics. We do not use advertising cookies.