Skip to content

Alpha Models

An alpha model is responsible for turning market state into candidate insights. It is not the place to manage the full order lifecycle. Its job is to decide when an opportunity exists and return an Insight.

AQE calls alpha models through a dedicated lifecycle:

  • start()
  • init(asset)
  • generate_insights(symbol)
aq-engine/src/core/alpha/mod.rs
pub trait AlphaModel {
fn version(&self) -> &str;
fn start(&mut self, ctx: &mut dyn StrategyContext);
fn init(&mut self, ctx: &mut dyn StrategyContext, asset: &Asset);
fn generate_insights(&mut self, ctx: &mut dyn StrategyContext, symbol: &str) -> AlphaResult;
}

Use start() for runtime-wide setup:

  • register indicators
  • set warm-up bars
  • configure state needed before any symbol-specific initialization

Use init() for per-asset setup:

  • initialize symbol-specific variables
  • read asset metadata
  • prepare per-symbol runtime state

Use generate_insights() to:

  • read the latest history
  • inspect indicators or variables
  • check asset metadata
  • create and return a new Insight

EmaPriceCrossover is a concrete alpha in AQE. It:

  • registers ATR and EMA
  • reads history
  • checks the latest and previous bars
  • uses asset metadata to decide whether short signals are allowed
  • returns an insight with entry, TP, SL, and TTL settings
aq-engine/src/core/alpha/ema_price_crossover.rs
fn generate_insights(&mut self, ctx: &mut dyn StrategyContext, symbol: &str) -> AlphaResult {
let Some(asset) = self.get_asset(ctx, symbol) else {
return AlphaResult::new(None, false, Some(format!("Asset {} not found", symbol)), self.name().to_string());
};
let history = match self.get_history(ctx, symbol) {
Some(history) if history.height() >= 2 => history,
_ => return AlphaResult::new(None, true, Some("Not enough history".to_string()), self.name().to_string()),
};
// derive signal, tp, sl, entry ...
let mut insight = Insight::new(
side,
symbol.to_string(),
StrategyType::Custom(self.name().to_string()),
ctx.timeframe().clone(),
confidence,
None,
);
insight
.set_limit_price(Some(entry))
.set_take_profit_levels(Some(vec![tp]))
.set_stop_loss(Some(sl))
.set_period_unfilled(Some(ttluf as u32))
.set_period_till_tp(Some(ttlf as u32));
AlphaResult::new(Some(insight), true, None, self.name().to_string())
}

TestEntry shows a simpler alpha shape:

  • reads recent price state
  • counts active insights per symbol
  • emits a new buy or sell insight when conditions are met

That makes it a useful reference for end-user code when you want a lightweight starting point.

Alpha models should not become full runtime managers. In most cases, avoid putting these concerns inside the alpha:

  • final quantity sizing
  • trade submission
  • close logic
  • stop-loss trigger checks
  • time-window filtering

Those fit better in insight pipes.

  • aq-engine/src/core/alpha/mod.rs
  • aq-engine/src/core/alpha/ema_price_crossover.rs
  • aq-engine/src/core/alpha/test_entry.rs