Skip to content

Complete Strategy Syntax Reference

This comprehensive guide covers every aspect of writing trading strategies for the backtesting engine.

Table of Contents

Backtest Configuration

Every strategy begins with BACKTEST_CONFIG—a dictionary that establishes your simulation's foundation. Think of it as setting the rules for your trading experiment: when it starts, when it ends, and how much capital you're working with.

python
BACKTEST_CONFIG = {
    'start_date': '20230610',  # YYYYMMDD format
    'end_date': '20230615',    # or '2023-06-15'
    'initial_capital': 100000,
}

The three required fields are intuitive: start_date, end_date (both accepting YYYYMMDD or YYYY-MM-DD formats), and initial_capital as your starting amount.

Symbol Auto-Discovery

Here's where it gets clever—you don't need to declare which stocks or ETFs your strategy will trade. The engine automatically discovers this by scanning your strategy code for any market['SYMBOL'] references. When you write market['SPY'].close or market['AAPL'].volume, the system knows to fetch data for SPY and AAPL.

This means you can focus on expressing your trading logic naturally, without maintaining separate symbol lists. The engine handles the data provisioning behind the scenes, ensuring all referenced symbols are available when your strategy runs.

Strategy Lifecycle Functions

Your strategy operates through lifecycle functions—Python functions that execute at specific moments during the simulation. Think of them as hooks that let you control what happens when the backtest starts, during each trading bar, at market open/close, and when everything ends.

The Required Function: on_bar_close

Every strategy must define on_bar_close—your main trading logic that runs for each minute bar during market hours:

python
def on_bar_close(market, portfolio, memory, session):
    # Your core trading logic here
    # Access market data, check positions, place orders
    return orders_or_none

This is where you analyze market conditions, check your current positions, and decide what trades to make. The function receives market data, portfolio information, persistent memory, and session details.

Optional Lifecycle Functions

The engine calls these functions at key moments, allowing you to structure complex strategies:

on_strategy_start(portfolio, memory) - Called once when your backtest begins. Perfect for initialization, loading reference data, or setting up indicators.

on_market_open(portfolio, memory, session) - Called at market open each trading day. Useful for daily rebalancing, gap analysis, or pre-market preparation.

on_market_close(portfolio, memory, session) - Called at market close each trading day. Ideal for end-of-day calculations, risk checks, or preparing for the next session.

on_strategy_end(portfolio, memory, results) - Called once when your backtest finishes. Use this for final calculations, result analysis, or cleanup.

The power lies in this structured approach—you can separate initialization logic from daily routines from core trading decisions, making complex strategies more manageable and easier to debug.

Strategy Parameters

Your lifecycle functions receive four parameters that serve as your interface to the simulation engine. These parameters give you everything needed to analyze markets, track positions, maintain state, and understand timing.

Market Data

The market parameter is a dictionary containing live market data for each symbol your strategy references. Access any symbol's data using bracket notation:

python
spy_data = market['SPY']
current_price = spy_data.close
volume = spy_data.volume
moving_avg = spy_data.sma(20)

Each market data object provides current bar information (open, high, low, close, volume, timestamp) plus technical indicators like sma(), ema(), rsi(), and bollinger_bands(). The data automatically updates as the simulation progresses through each minute bar.

Portfolio

The portfolio parameter tracks your account state and positions. It provides both informational properties and decision-making tools:

Account Information: portfolio.cash (available funds), portfolio.equity (total value), portfolio.buying_power (purchasing capacity)

Position Tracking: portfolio.position('AAPL') returns shares held, portfolio.market_value('AAPL') shows current dollar value

Performance Metrics: portfolio.total_return (percentage gain/loss), portfolio.unrealized_pnl() (paper profits/losses)

The portfolio updates automatically after each trade execution, so you always see current state when making new decisions.

Memory

The memory parameter is your persistent storage—a regular Python dictionary that maintains state between function calls. Unlike local variables that disappear after each bar, memory persists throughout the entire simulation:

python
# Track trade timing
memory['last_trade_date'] = session.current_date

# Maintain counters
memory['trades_today'] = memory.get('trades_today', 0) + 1

# Store calculation results
memory['portfolio_high'] = max(memory.get('portfolio_high', 0), portfolio.equity)

This is where you store indicators, flags, counters, or any state needed across multiple bars. Use memory.get(key, default) for safe access to potentially unset keys.

Session

The optional session parameter provides timing and progress information about the current simulation. It's read-only metadata useful for time-based logic:

Timing: session.current_timestamp, session.current_date, session.is_market_open, session.is_market_close

Progress: session.minute_index, session.progress_pct, session.bars_remaining

Most strategies don't need session information, but it's valuable for calendar-based rules (monthly rebalancing), progress tracking, or market session logic (different behavior at open vs. close).

Data Access (Legacy Examples)

Getting Symbol Data

python
# Access data for a specific symbol
aapl = market['AAPL']
googl = market['GOOGL']

# Available symbols: AAPL, GOOGL, MSFT, TSLA, AMZN

OHLCV Properties

python
# Current bar data
current_open = aapl.open      # Opening price
current_high = aapl.high      # High price
current_low = aapl.low        # Low price
current_close = aapl.close    # Closing price
current_volume = aapl.volume  # Volume

# Typical price calculations
typical_price = (aapl.high + aapl.low + aapl.close) / 3
vwap_estimate = (aapl.high + aapl.low) / 2  # Simplified VWAP

Historical Data Access

python
# Get historical values
close_history = aapl.history('close', 10)  # Last 10 closing prices
volume_history = aapl.history('volume', 5)  # Last 5 volume values

# Calculate custom metrics
avg_volume = sum(volume_history) / len(volume_history)
price_change = close_history[-1] - close_history[0]

Portfolio Management

Account Information

python
# Basic account metrics
cash_available = portfolio.cash           # Available cash
total_equity = portfolio.equity          # Total portfolio value
initial_capital = portfolio.initial_capital  # Starting capital
buying_power = portfolio.buying_power    # Available for trading

# Performance metrics
total_return = portfolio.total_return    # Percentage return
current_pnl = portfolio.equity - portfolio.initial_capital

Position Management

python
# Check positions
aapl_shares = portfolio.position('AAPL')  # Number of shares held
has_position = aapl_shares > 0           # Boolean check

# Get all positions
all_positions = portfolio.positions      # Dict of all positions
total_positions = sum(all_positions.values())

# Position metrics
if aapl_shares > 0:
    position_value = aapl_shares * aapl.close
    position_weight = position_value / portfolio.equity

Order Creation

Basic Order Format

python
# Standard order dictionary
order = {
    'symbol': 'AAPL',      # Stock symbol
    'action': 'buy',        # 'buy' or 'sell'
    'quantity': 100         # Number of shares
}

# Return single order
return order

# Return multiple orders
return [order1, order2, order3]

# No action
return None

Quantity Specifications

python
# Fixed quantity
return {'symbol': 'AAPL', 'action': 'buy', 'quantity': 100}

# All shares (for selling)
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': 'all'}

# Dollar amount
return {'symbol': 'AAPL', 'action': 'buy', 'quantity': '$5000'}

# Portfolio percentage
return {'symbol': 'AAPL', 'action': 'buy', 'quantity': '10%'}

# Position percentage (for selling)
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': '50%:position'}

# Maximum affordable
return {'symbol': 'AAPL', 'action': 'buy', 'quantity': 'max'}

# Half of available cash
return {'symbol': 'AAPL', 'action': 'buy', 'quantity': 'half'}

Technical Indicators

Moving Averages

python
# Simple Moving Average (SMA)
sma_20 = aapl.sma(20)      # 20-period SMA
sma_50 = aapl.sma(50)      # 50-period SMA
sma_200 = aapl.sma(200)    # 200-period SMA

# Exponential Moving Average (EMA)
ema_12 = aapl.ema(12)      # 12-period EMA
ema_26 = aapl.ema(26)      # 26-period EMA

# Moving average crossover
golden_cross = sma_50 > sma_200 and previous_sma_50 <= previous_sma_200
death_cross = sma_50 < sma_200 and previous_sma_50 >= previous_sma_200

Momentum Indicators

python
# Relative Strength Index (RSI)
rsi = aapl.rsi(14)         # 14-period RSI (0-100)

# RSI levels
oversold = rsi < 30        # Potential buy signal
overbought = rsi > 70      # Potential sell signal
neutral = 30 <= rsi <= 70  # Neutral zone

# RSI divergence (manual calculation)
price_higher = aapl.close > previous_close
rsi_lower = rsi < previous_rsi
bearish_divergence = price_higher and rsi_lower

Custom Indicators

python
# Bollinger Bands (manual calculation)
sma = aapl.sma(20)
closes = aapl.history('close', 20)
std_dev = np.std(closes) if 'np' in globals() else 0
upper_band = sma + (2 * std_dev)
lower_band = sma - (2 * std_dev)

# Price channels
highest_20 = max(aapl.history('high', 20))
lowest_20 = min(aapl.history('low', 20))
channel_width = highest_20 - lowest_20

# Volume indicators
current_volume = aapl.volume
avg_volume = sum(aapl.history('volume', 20)) / 20
volume_ratio = current_volume / avg_volume
high_volume = volume_ratio > 1.5

State Management

Initializing Memory

python
# Initialize memory variables
if 'initialized' not in memory:
    memory['initialized'] = True
    memory['entry_price'] = 0
    memory['highest_price'] = 0
    memory['trade_count'] = 0
    memory['winning_trades'] = 0
    memory['losing_trades'] = 0

Tracking Trade Information

python
# Store entry information
if entering_position:
    memory['entry_price'] = aapl.close
    memory['entry_time'] = session.current_time
    memory['entry_reason'] = 'SMA crossover'

# Track highest price (for trailing stops)
if portfolio.position('AAPL') > 0:
    memory['highest_price'] = max(memory.get('highest_price', 0), aapl.close)

# Record trade results
if exiting_position:
    profit = (exit_price - memory['entry_price']) * shares
    memory['trade_count'] += 1
    if profit > 0:
        memory['winning_trades'] += 1
    else:
        memory['losing_trades'] += 1

Pattern Detection with State

python
# Track consecutive signals
if 'consecutive_ups' not in state:
    state['consecutive_ups'] = 0

if aapl.close > aapl.sma(20):
    state['consecutive_ups'] += 1
else:
    state['consecutive_ups'] = 0

# Trigger after 3 consecutive signals
if state['consecutive_ups'] >= 3:
    # Strong uptrend confirmed
    pass

Helper Functions

Buy and Sell Helpers

python
# Import helpers (automatically available)
# from core.order_utils import buy, sell, sell_all

# Simple buy
return buy('AAPL', 100)                    # Buy 100 shares
return buy('AAPL', '$5000')                # Buy $5000 worth
return buy('AAPL', '10%')                  # Buy with 10% of portfolio

# Simple sell
return sell('AAPL', 50)                    # Sell 50 shares
return sell('AAPL', '50%:position')        # Sell half of position
return sell_all('AAPL')                    # Sell entire position

# Multiple orders
orders = []
orders.append(buy('AAPL', 100))
orders.append(sell('GOOGL', 'all'))
return orders

Advanced Order Types

python
# Limit orders
return buy_limit('AAPL', 100, limit_price=150.00)
return sell_limit('AAPL', 'all', limit_price=160.00)

# Stop orders
return sell_stop('AAPL', 'all', stop_price=145.00)

# Scale in/out
return scale_in('AAPL', '$10000', num_orders=3)   # Split into 3 buys
return scale_out('AAPL', num_orders=4)            # Exit in 4 parts

# Bracket orders (entry + stop loss + take profit)
return bracket_order('AAPL', '10%', 
                    stop_loss_pct=0.05,    # 5% stop loss
                    take_profit_pct=0.15)   # 15% take profit

Portfolio Operations

python
# Rebalance to target allocations
target_allocations = {
    'AAPL': 0.40,   # 40% in AAPL
    'GOOGL': 0.35,  # 35% in GOOGL
    'MSFT': 0.25    # 25% in MSFT
}
return rebalance_portfolio(target_allocations, min_trade_amount=500)

# Dollar cost averaging
return dollar_cost_average('AAPL', '$5000', 
                          num_periods=5,    # Split into 5 buys
                          period_days=7)    # One per week

Advanced Features

Multi-Symbol Strategies

python
def strategy(data_contexts, portfolio, state):
    """Trade multiple symbols simultaneously"""
    
    orders = []
    
    # Check each symbol
    for symbol in ['AAPL', 'GOOGL', 'MSFT']:
        data = data_contexts[symbol]
        position = portfolio.position(symbol)
        
        # Apply same logic to each symbol
        if data.rsi(14) < 30 and position == 0:
            orders.append(buy(symbol, '5%'))  # 5% of portfolio each
        elif data.rsi(14) > 70 and position > 0:
            orders.append(sell_all(symbol))
    
    return orders if orders else None

Pairs Trading

python
def strategy(data_contexts, portfolio, state):
    """Trade the spread between two correlated assets"""
    
    aapl = data_contexts['AAPL']
    msft = data_contexts['MSFT']
    
    # Calculate spread
    aapl_norm = aapl.close / aapl.sma(20)
    msft_norm = msft.close / msft.sma(20)
    spread = aapl_norm - msft_norm
    
    # Mean reversion on spread
    if spread > 0.05:  # AAPL expensive relative to MSFT
        return [
            sell('AAPL', '10%:position') if portfolio.position('AAPL') > 0 else None,
            buy('MSFT', '10%')
        ]
    elif spread < -0.05:  # AAPL cheap relative to MSFT
        return [
            buy('AAPL', '10%'),
            sell('MSFT', '10%:position') if portfolio.position('MSFT') > 0 else None
        ]
    
    return None

Conditional Logic Patterns

python
def strategy(data_contexts, portfolio, state):
    """Complex conditional logic example"""
    
    aapl = data_contexts['AAPL']
    
    # Market regime detection
    trending = abs(aapl.sma(10) - aapl.sma(30)) / aapl.sma(30) > 0.02
    volatile = (aapl.high - aapl.low) / aapl.close > 0.03
    
    # Adjust strategy based on market conditions
    if trending and not volatile:
        # Trend following in calm markets
        if aapl.sma(10) > aapl.sma(30):
            return buy('AAPL', '15%')
    elif not trending and volatile:
        # Mean reversion in choppy markets
        if aapl.rsi(14) < 30:
            return buy('AAPL', '5%')
        elif aapl.rsi(14) > 70:
            return sell_all('AAPL')
    
    return None

Risk Management Patterns

python
def strategy(data_contexts, portfolio, state):
    """Comprehensive risk management"""
    
    aapl = data_contexts['AAPL']
    position = portfolio.position('AAPL')
    
    if position > 0:
        # Calculate current P&L
        entry_price = state.get('entry_price', aapl.close)
        current_pnl = (aapl.close - entry_price) / entry_price
        
        # Dynamic exit conditions
        if current_pnl <= -0.05:  # 5% stop loss
            print("Stop loss triggered")
            return sell_all('AAPL')
        
        elif current_pnl >= 0.20:  # Take partial profits at 20%
            print("Taking partial profits")
            return sell('AAPL', '50%:position')
        
        elif current_pnl >= 0.10:  # Tighten stop after 10% gain
            # Implement trailing stop logic
            highest = state.get('highest_price', aapl.close)
            if aapl.close < highest * 0.95:  # 5% trailing stop
                print("Trailing stop triggered")
                return sell_all('AAPL')
            state['highest_price'] = max(highest, aapl.close)
    
    # Entry logic here...
    return None

Global Variables Support

python
# Define strategy-level constants
FAST_PERIOD = 10
SLOW_PERIOD = 30
MAX_POSITION_SIZE = 1000
RISK_PER_TRADE = 0.02

# Define strategy configuration
SYMBOLS_TO_TRADE = ['AAPL', 'GOOGL', 'MSFT']
RSI_OVERSOLD = 30
RSI_OVERBOUGHT = 70

def strategy(data_contexts, portfolio, state):
    """Strategy using global variables"""
    
    # Global variables are automatically accessible
    for symbol in SYMBOLS_TO_TRADE:
        data = data_contexts[symbol]
        
        fast_ma = data.sma(FAST_PERIOD)
        slow_ma = data.sma(SLOW_PERIOD)
        rsi = data.rsi(14)
        
        if rsi < RSI_OVERSOLD:
            shares = min(MAX_POSITION_SIZE, int(portfolio.equity * RISK_PER_TRADE / data.close))
            return buy(symbol, shares)
    
    return None

Debugging and Logging

python
def strategy(data_contexts, portfolio, state):
    """Debugging techniques"""
    
    aapl = data_contexts['AAPL']
    
    # Print current state
    print(f"Time: {aapl.timestamp}")
    print(f"Price: ${aapl.close:.2f}")
    print(f"RSI: {aapl.rsi(14):.1f}")
    print(f"Position: {portfolio.position('AAPL')} shares")
    print(f"Cash: ${portfolio.cash:.2f}")
    print(f"Equity: ${portfolio.equity:.2f}")
    
    # Conditional debugging
    if state.get('debug_mode', False):
        print(f"Detailed state: {state}")
    
    # Track specific events
    if entering_trade:
        print(f"ENTRY: Buying at ${aapl.close:.2f}")
        print(f"  Reason: {entry_reason}")
        print(f"  Size: {shares} shares")
    
    return None

Common Pitfalls to Avoid

python
# WRONG: Checking for None incorrectly
if sma == None:  # Don't use ==
    return None

# RIGHT: Check for NaN
if sma != sma:  # NaN is not equal to itself
    return None

# WRONG: Accessing future data
tomorrow_price = aapl.history('close', -1)  # Can't see future!

# RIGHT: Only use historical data
yesterday_price = aapl.history('close', 2)[-2]

# WRONG: Using unavailable libraries
import numpy as np  # Not available in sandbox

# RIGHT: Use built-in functions
average = sum(values) / len(values)

# WRONG: Modifying data_contexts
data_contexts['AAPL'].close = 100  # Don't modify!

# RIGHT: Use local variables
adjusted_price = aapl.close * 1.01

Performance Tips

  1. Cache calculations in state to avoid recalculating
  2. Check indicators are ready before using (NaN check)
  3. Minimize print statements in production strategies
  4. Use portfolio percentages instead of fixed shares
  5. Always validate orders before returning

This reference covers all available syntax and features. For specific examples, see our Example Strategies section.

Test your trading strategies risk-free with professional backtesting.