Appearance
Complete Strategy Syntax Reference
This comprehensive guide covers every aspect of writing trading strategies for the backtesting engine.
Table of Contents
- Backtest Configuration
- Strategy Function Signature
- Data Access
- Portfolio Management
- Order Creation
- Technical Indicators
- State Management
- Helper Functions
- Advanced Features
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
- Cache calculations in state to avoid recalculating
- Check indicators are ready before using (NaN check)
- Minimize print statements in production strategies
- Use portfolio percentages instead of fixed shares
- Always validate orders before returning
This reference covers all available syntax and features. For specific examples, see our Example Strategies section.