Appearance
Complete Strategy Syntax Reference
This comprehensive guide covers every aspect of writing trading strategies for our backtesting engine.
Table of Contents
- Strategy Function Signature
- Data Access
- Portfolio Management
- Order Creation
- Technical Indicators
- State Management
- Helper Functions
- Advanced Features
Strategy Function Signature
Every strategy must implement this function:
python
def on_bar_close(market, portfolio, memory, session):
"""
Main strategy function called for each trading bar during market hours
Args:
market (dict): Market data contexts for each symbol
portfolio (PortfolioContext): Account and position information
memory (dict): Persistent storage between calls
session (SessionInfo): Current simulation metadata (optional)
Returns:
dict | list[dict] | None: Order(s) to execute or None
"""
# Your strategy logic here
return order_or_none
Context Variables Reference
The strategy function receives four context variables that provide access to market data, portfolio information, persistent state, and session metadata.
Market Data Context
market
(DataContext)
Provides market data and technical indicators for a single symbol
Properties
close
floatreadonlyCurrent bar close price
Example:
market["AAPL"].close
high
floatreadonlyCurrent bar high price
Example:
market["AAPL"].high
low
floatreadonlyCurrent bar low price
Example:
market["AAPL"].low
open
floatreadonlyCurrent bar open price
Example:
market["AAPL"].open
timestamp
TimestampreadonlyCurrent bar timestamp
Example:
market["AAPL"].timestamp
volume
intreadonlyCurrent bar volume
Example:
market["AAPL"].volume
Methods
ema(period: int) -> float
→ floatExponential Moving Average
Parameters:
period
(int)- period parameter
Returns:
float - EMA value or NaN if insufficient data
Example:
market["AAPL"].ema(20)
history(field: str, periods: int) -> List[float]
→ List[float]Get historical values for a field
Parameters:
field
(str)- field parameterperiods
(int)- periods parameter
Returns:
List[float] - List of historical values (most recent last)
Example:
market["AAPL"].history('close', 20)
rsi(period: int = 14) -> float
→ floatRelative Strength Index
Parameters:
period
(int)optional- period parameterDefault:14
Returns:
float - RSI value between 0 and 100, or NaN if insufficient data
Example:
market["AAPL"].rsi(20)
set_current_index(index: int)
→ AnySet the current bar index for backtesting
Parameters:
index
(int)- index parameter
Example:
market["AAPL"].set_current_index(...)
sma(period: int) -> float
→ floatSimple Moving Average
Parameters:
period
(int)- period parameter
Returns:
float - SMA value or NaN if insufficient data
Example:
market["AAPL"].sma(20)
Usage Examples
Basic Price Access
aapl = market['AAPL']
current_price = aapl.close
current_volume = aapl.volume
Technical Indicators
# Moving averages
sma_20 = aapl.sma(20)
sma_50 = aapl.sma(50)
# Momentum indicators
rsi = aapl.rsi(14)
if rsi < 30: # Oversold
return buy('AAPL', 100)
Historical Data
# Get last 10 closing prices
closes = aapl.history('close', 10)
avg_price = sum(closes) / len(closes)
Portfolio Context
portfolio
(PortfolioContext)
Provides portfolio information and performance metrics
Properties
buying_power
floatreadonlyAvailable buying power (same as cash for now)
Example:
portfoliocontext.buying_power
equity
floatreadonlyTotal portfolio value (cash + positions market value)
Example:
portfoliocontext.equity
total_return
floatreadonlyTotal return percentage
Example:
portfoliocontext.total_return
Methods
execute_buy(symbol: str, quantity: int, price: float, timestamp: str) -> bool
→ boolExecute a buy order (cash account behavior)
Parameters:
symbol
(str)- symbol parameterquantity
(int)- quantity parameterprice
(float)- price parametertimestamp
(str)- timestamp parameter
Returns:
bool - True if successful, False if insufficient funds
Example:
portfoliocontext.execute_buy('AAPL', ..., ..., ...)
execute_sell(symbol: str, quantity: int, price: float, timestamp: str) -> bool
→ boolExecute a sell order (cash account behavior)
Parameters:
symbol
(str)- symbol parameterquantity
(int)- quantity parameterprice
(float)- price parametertimestamp
(str)- timestamp parameter
Returns:
bool - True if successful, False if insufficient shares
Example:
portfoliocontext.execute_sell('AAPL', ..., ..., ...)
get_last_price(symbol: str) -> Optional[float]
→ Optional[float]Get the last known price for a symbol
Parameters:
symbol
(str)- symbol parameter
Returns:
Optional[float] - Last price or None if not available
Example:
portfoliocontext.get_last_price('AAPL')
get_positions() -> Dict[str, int]
→ Dict[str, int]Get all current positions
Returns:
Dict[str, int] - Dictionary of symbol to share count (excludes zero positions)
Example:
portfoliocontext.get_positions()
market_value(symbol: str) -> float
→ floatCurrent market value of position
Parameters:
symbol
(str)- symbol parameter
Returns:
float - Current market value of the position
Example:
portfoliocontext.market_value('AAPL')
position(symbol: str) -> int
→ intGet current position size for a symbol
Parameters:
symbol
(str)- symbol parameter
Returns:
int - Number of shares held (0 if no position)
Example:
portfoliocontext.position('AAPL')
realized_pnl() -> float
→ floatTotal realized P&L from completed trades
Returns:
float - Sum of realized P&L from trade history
Example:
portfoliocontext.realized_pnl()
record_equity_snapshot(timestamp: str)
→ AnyRecord current equity for equity curve
Parameters:
timestamp
(str)- timestamp parameter
Example:
portfoliocontext.record_equity_snapshot(...)
shares_for_dollars(dollars: float, price: float) -> int
→ intCalculate shares for given dollar amount
Parameters:
dollars
(float)- dollars parameterprice
(float)- price parameter
Returns:
int - Number of shares that can be bought (whole shares only)
Example:
portfoliocontext.shares_for_dollars(..., ...)
unrealized_pnl(symbol: Optional[str] = None) -> float
→ floatUnrealized P&L for a symbol or total portfolio
Parameters:
symbol
(Optional[str])optional- symbol parameterDefault:None
Returns:
float - Unrealized profit/loss
Example:
portfoliocontext.unrealized_pnl('AAPL')
update_prices(prices: Dict[str, float])
→ AnyUpdate last known prices for positions
Parameters:
prices
(Dict[str, float])- prices parameter
Example:
portfoliocontext.update_prices(...)
Usage Examples
Position Information
# Check current positions
aapl_shares = portfolio.position('AAPL')
total_equity = portfolio.equity
available_cash = portfolio.cash
Performance Metrics
# Calculate performance
total_return_pct = portfolio.total_return * 100
current_value = portfolio.equity
profit_loss = portfolio.equity - portfolio.initial_capital
Memory Context
memory
(dict)
Persistent dictionary for storing strategy state between function calls. Use this to track variables that need to persist across bars.
Methods
get(key, default=None)
→ AnyGet value for key, or return default if key not found
Parameters:
key
(str)- Dictionary key to look updefault
(Any)optional- Default value if key not found
Returns:
Any - Value associated with key, or default
Example:
entry_price = memory.get('entry_price', 0)
setdefault(key, default)
→ AnyGet value for key, or set and return default if key not found
Parameters:
key
(str)- Dictionary keydefault
(Any)- Default value to set if key not found
Returns:
Any - Value associated with key
Example:
trade_count = memory.setdefault('trade_count', 0)
Usage Examples
Initializing Memory Variables
if 'initialized' not in memory:
memory['initialized'] = True
memory['entry_price'] = 0
memory['trade_count'] = 0
Tracking Trade State
# Store entry information
if entering_position:
memory['entry_price'] = market['AAPL'].close
memory['entry_time'] = session.current_time
# Track performance
memory['trade_count'] = memory.get('trade_count', 0) + 1
Session Context
session
(SessionInfo)
Immutable container for simulation metadata and timing information. All fields are read-only to prevent accidental modification by strategies.
Methods
to_dict() -> dict
→ dictConvert to dictionary for backward compatibility
Example:
sessioninfo.to_dict()
Usage Examples
Time Information
# Access current time
current_time = session.current_time
current_date = session.current_date
print(f"Trading at {current_time} on {current_date}")
Progress Tracking
# Monitor backtest progress
progress = session.progress_pct
remaining = session.bars_remaining
print(f"Backtest {progress:.1f}% complete, {remaining} bars remaining")
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.