Start here

Build a strategy

Learn the Algorithm contract: configuration, lifecycle hooks, data access, scheduling, orders, sizing, and risk controls.

An AlphaLens strategy is a Python class that subclasses Algorithm. The class declares the run configuration, receives completed bars, reads history, and submits target weights or orders. The same class can run as a backtest or as a local live deployment.

Strategy anatomy

Every strategy has three layers:

LayerWhat it contains
Configurationstart, end, universe, resolution, initial_cash, benchmark_symbol, sweep settings
Lifecycleinitialize, on_warmup_finished, on_data, scheduled methods, order events
Trading logichistory, history_array, history_arrays, set_holdings, orders, risk limits

Here is the smallest useful shape:

python
from alphalens_core import Algorithm


class MyStrategy(Algorithm):
    start = "2020-01-01"
    end = "2025-12-31"
    universe = ["SPY"]
    resolution = "1day"
    initial_cash = 100_000
    benchmark_symbol = "SPY"

    def initialize(self):
        self.set_warmup(50)

    def on_data(self, slice):
        history = self.history("SPY", 50)
        if len(history) < 50:
            return

        fast = history["close"].tail(10).mean()
        slow = history["close"].mean()
        self.set_holdings("SPY", 1.0 if fast > slow else 0.0)

Run it with:

bash
alphalens run --strategy my_strategy:MyStrategy

Configuration

Set defaults on the class so the CLI and Python API can run the strategy without a separate config file.

AttributePurpose
start / endBacktest date window
universeList of symbols to subscribe to
resolutionBar size, such as 1day or 5minute
initial_cashStarting capital for backtests
benchmark_symbolBenchmark used for relative performance
track_runsPersist local run metadata
cloud_enabledSync run data to AlphaLens when auth is configured
progressEmit low-frequency backtest progress logs
sweep_gridParameter values for batch research

Constructor parameters are useful for values you want to override with --param.

python
class Breakout(Algorithm):
    start = "2021-01-01"
    end = "2025-12-31"
    universe = ["SPY", "QQQ", "IWM"]
    resolution = "1day"
    benchmark_symbol = "SPY"

    lookback = 63
    threshold = 0.05

    sweep_grid = {
        "lookback": [21, 63, 126],
        "threshold": [0.03, 0.05, 0.08],
    }

Run one variant:

bash
alphalens run --strategy my_strategy:Breakout --param lookback=126 --param threshold=0.03

Lifecycle hooks

Use initialize for setup only. Configuration setters such as set_warmup, set_universe, and set_brokerage_model must be called there before the engine starts.

HookWhen it runs
initialize()Once before data starts
on_warmup_finished()Once after warmup history is seeded
on_data(slice)On every completed bar
on_order_event(event)When an order updates or fills
on_securities_changed(changes)When the active universe changes
on_end_of_day(symbol)End-of-day event for a symbol

For intraday strategies, keep on_data cheap. Let it ingest the bar and update state; use scheduled methods for heavier rebalances.

Warmup

Warmup gives the strategy enough history before the tradable window starts.

python
def initialize(self):
    self.set_warmup(252)

Use replay=False when your strategy only needs the retained history window and does not need every warmup bar replayed through on_data.

python
def initialize(self):
    self.set_warmup(252, replay=False)

This matters for large intraday universes because replaying every warmup bar can be expensive.

Data access

slice contains the current completed bar. history returns a pandas DataFrame for one symbol or a multi-index DataFrame for many symbols.

python
def on_data(self, slice):
    if "SPY" not in slice:
        return

    close = self.history("SPY", 20)["close"]

For faster cross-sectional strategies, use NumPy arrays:

python
timestamps, values, present = self.history_arrays(
    ["SPY", "QQQ", "IWM"],
    126,
    ["close"],
)
closes = values[:, :, 0]

history_arrays returns (timestamps, values, present) where values is shaped [periods, symbols, fields].

Scheduling

Scheduled methods keep signal generation and execution cadence explicit.

python
from alphalens_core import Algorithm, DateRules, TimeRules


class ScheduledStrategy(Algorithm):
    resolution = "5minute"

    def initialize(self):
        self.set_warmup(390)
        self.schedule.on(
            DateRules.every_day(),
            TimeRules.market_open(minutes_after=60),
            self.rebalance,
        )

    def on_data(self, slice):
        # Keep this lightweight for intraday runs.
        self.last_slice = slice

    def rebalance(self):
        self.set_holdings("SPY", 1.0, tag="scheduled-rebalance")

Use scheduling when you want 5-minute bars for state but only want to place orders at 10:00, 11:00, 12:00, and similar decision points.

Orders and target weights

Most strategies should use set_holdings. It computes the trade needed to reach a target portfolio weight.

python
self.set_holdings("SPY", 1.0, tag="risk-on")
self.set_holdings("TLT", 0.0, tag="risk-off")
self.set_holdings("SH", -0.25, tag="short-hedge")

Use direct orders when you need order-type control.

MethodBehavior
market_order(symbol, quantity)Fill at the next bar open
market_on_open(symbol, quantity)Explicit next-open order
market_on_close(symbol, quantity)Fill at the next bar close
limit_order(symbol, quantity, limit_price)Fill if the next bar crosses the limit
stop_order(symbol, quantity, stop_price)Trigger stop-market order
stop_limit_order(symbol, quantity, stop_price, limit_price)Trigger stop then require limit fill
liquidate(symbol)Close one symbol or all invested symbols

set_holdings supports long, cash, and short targets. A target weight above 1.0 uses leverage when the brokerage model allows it.

Risk controls

Risk helpers are optional but useful for reusable guardrails.

python
def initialize(self):
    self.set_warmup(252)
    self.set_max_position_size("SPY", 0.50)
    self.set_stop_loss("SPY", 0.08)

set_max_position_size clamps future set_holdings calls. Manual order methods are not clamped.

You can also size by realized volatility:

python
qty = self.vol_targeted_quantity("SPY", target_annual_vol=0.15, lookback=63)
self.market_order("SPY", qty, tag="vol-target")

Complete example

This example rotates into the strongest positive-momentum assets once per day.

python
import numpy as np
from alphalens_core import Algorithm, DateRules, TimeRules


class MomentumRotation(Algorithm):
    start = "2018-01-01"
    end = "2025-12-31"
    universe = ["SPY", "QQQ", "IWM", "TLT", "GLD", "DBC"]
    resolution = "1day"
    initial_cash = 100_000
    benchmark_symbol = "SPY"

    lookback = 126
    top_n = 2
    max_weight = 0.50

    sweep_grid = {
        "lookback": [63, 126, 189],
        "top_n": [1, 2, 3],
    }

    def initialize(self):
        self.set_warmup(self.lookback + 1, replay=False)
        self.schedule.on(
            DateRules.every_day(),
            TimeRules.market_open(minutes_after=1),
            self.rebalance,
        )

    def rebalance(self):
        symbols = [security.symbol for security in self.securities.values()]
        if not symbols:
            return

        _, values, present = self.history_arrays(
            symbols,
            self.lookback + 1,
            ["close"],
        )
        closes = values[:, :, 0]
        valid = present.all(axis=0) & np.isfinite(closes).all(axis=0)

        momentum = closes[-1] / closes[0] - 1.0
        scores = np.where(valid, momentum, -np.inf)
        ranked = np.argsort(scores)[::-1]
        winners = [
            symbols[i].ticker
            for i in ranked[: self.top_n]
            if scores[i] > 0
        ]
        winner_set = set(winners)
        weight = min(1.0 / max(len(winners), 1), self.max_weight)

        for symbol in symbols:
            target = weight if symbol.ticker in winner_set else 0.0
            self.set_holdings(symbol, target, tag="momentum-rotation")

Run it locally:

bash
ALPHALENS_API_KEY=alens_... alphalens run --strategy my_strategy:MomentumRotation

Then open Strategy Center and select the synced backtest.

What to avoid

  • Do not fetch future data inside strategy code.
  • Do not put API keys or broker credentials on the strategy class.
  • Do not do expensive model training inside every on_data call.
  • Do not rely on telemetry writes for trading correctness. Cloud sync is best-effort.
  • Do not optimize only the headline return. Use drawdown, turnover, benchmark comparison, and out-of-sample checks.