Appearance
State Dictionary
The state dictionary is your strategy's memory, persisting data across function calls. It contains system information, configuration parameters, and your custom variables.
System Properties
These are automatically provided by the backtesting engine:
minute_index(int) - Current minute in backtest (0-based)current_timestamp(datetime) - Current bar timestamp (Eastern Time)session_info(dict) - Market session metadataprogress(float) - Backtest completion percentage (0.0-1.0)total_minutes(int) - Total minutes in the backtest
Configuration Parameters (New in Hybrid Architecture)
These configuration parameters are automatically injected from the API/UI parameters:
symbols(list) - List of symbols being backtestedstart_date(str) - Backtest start dateend_date(str) - Backtest end dateinitial_capital(float) - Starting capital amountbacktest_config(dict) - Complete configuration metadata
python
def on_strategy_start(portfolio, state):
# Access configuration parameters
symbols = state.get('symbols', ['AAPL'])
start_date = state.get('start_date', 'Not specified')
end_date = state.get('end_date', 'Not specified')
initial_capital = state.get('initial_capital', 100000)
print(f"Trading {len(symbols)} symbols: {symbols}")
print(f"Period: {start_date} to {end_date}")
print(f"Capital: ${initial_capital:,.2f}")
def on_bar_close(data_contexts, portfolio, state):
# Access system info
current_minute = state['minute_index']
timestamp = state['current_timestamp']
progress = state['progress']
print(f"Minute {current_minute}/{state['total_minutes']} ({progress*100:.1f}%)")Custom Storage
Use state to store any data you need to persist between function calls:
Basic Usage
python
def on_strategy_start(portfolio, state):
"""Initialize custom state variables"""
state['trade_count'] = 0
state['max_portfolio_value'] = portfolio.initial_capital
state['signals_history'] = []
def on_bar_close(data_contexts, portfolio, state):
"""Use and update custom state"""
aapl = data_contexts['AAPL']
# Update tracking variables
state['max_portfolio_value'] = max(state['max_portfolio_value'], portfolio.equity)
# Store signal history
rsi = aapl.rsi(14)
if rsi == rsi: # Not NaN
state['signals_history'].append(rsi)
# Keep only last 100 values
if len(state['signals_history']) > 100:
state['signals_history'] = state['signals_history'][-100:]Common Use Cases
Trade Counting and Tracking
python
def on_bar_close(data_contexts, portfolio, state):
"""Track trading activity"""
# Initialize counters if first run
if 'trades_today' not in state:
state['trades_today'] = 0
state['total_trades'] = 0
state['winning_trades'] = 0
state['losing_trades'] = 0
aapl = data_contexts['AAPL']
position = portfolio.position('AAPL')
# Example trading logic
if should_buy(aapl) and position == 0:
state['trades_today'] += 1
state['total_trades'] += 1
return {"symbol": "AAPL", "action": "buy", "quantity": 100}Signal History and Crossovers
python
def on_bar_close(data_contexts, portfolio, state):
"""Detect indicator crossovers"""
aapl = data_contexts['AAPL']
# Calculate current indicators
sma_fast = aapl.sma(10)
sma_slow = aapl.sma(20)
# Get previous values from state
prev_fast = state.get('prev_sma_fast', sma_fast)
prev_slow = state.get('prev_sma_slow', sma_slow)
# Detect golden cross (fast above slow for first time)
if sma_fast > sma_slow and prev_fast <= prev_slow:
print("Golden cross detected!")
state['last_golden_cross'] = state['current_timestamp']
if portfolio.position('AAPL') == 0:
return {"symbol": "AAPL", "action": "buy", "quantity": 100}
# Store current values for next iteration
state['prev_sma_fast'] = sma_fast
state['prev_sma_slow'] = sma_slowRisk Management State
python
def on_strategy_start(portfolio, state):
"""Initialize risk management"""
state['daily_loss_limit'] = portfolio.initial_capital * 0.02 # 2% daily loss limit
state['position_size_multiplier'] = 1.0
state['consecutive_losses'] = 0
state['max_consecutive_losses'] = 0
def on_bar_close(data_contexts, portfolio, state):
"""Apply risk management rules"""
# Check daily P&L
daily_start_equity = state.get('daily_start_equity', portfolio.initial_capital)
daily_pnl = portfolio.equity - daily_start_equity
# Stop trading if daily loss limit hit
if daily_pnl < -state['daily_loss_limit']:
state['trading_halted'] = True
return None
# Skip trading if halted
if state.get('trading_halted', False):
return None
# Adjust position size based on recent performance
base_quantity = int(100 * state['position_size_multiplier'])
# Your trading logic with adjusted size...Performance Tracking
python
def on_market_open(portfolio, state, market_data):
"""Reset daily tracking"""
state['daily_start_equity'] = portfolio.equity
state['daily_trades'] = 0
def on_market_close(portfolio, state, market_data):
"""Calculate daily performance"""
daily_pnl = portfolio.equity - state['daily_start_equity']
daily_return = daily_pnl / state['daily_start_equity']
# Track daily returns
if 'daily_returns' not in state:
state['daily_returns'] = []
state['daily_returns'].append(daily_return)
# Calculate rolling metrics
if len(state['daily_returns']) >= 30:
recent_returns = state['daily_returns'][-30:]
avg_return = sum(recent_returns) / len(recent_returns)
volatility = (sum((r - avg_return)**2 for r in recent_returns) / len(recent_returns)) ** 0.5
state['30day_avg_return'] = avg_return
state['30day_volatility'] = volatility
print(f"30-day avg return: {avg_return*100:.2f}%")
print(f"30-day volatility: {volatility*100:.2f}%")Multi-Symbol State Management
python
def on_bar_close(data_contexts, portfolio, state):
"""Manage state for multiple symbols"""
# Initialize symbol-specific state
for symbol in data_contexts:
if f'{symbol}_signals' not in state:
state[f'{symbol}_signals'] = []
state[f'{symbol}_last_trade'] = None
orders = []
for symbol, data in data_contexts.items():
# Get symbol-specific state
signals_key = f'{symbol}_signals'
last_trade_key = f'{symbol}_last_trade'
# Calculate signal
rsi = data.rsi(14)
if rsi == rsi: # Not NaN
state[signals_key].append(rsi)
# Keep only recent signals
if len(state[signals_key]) > 50:
state[signals_key] = state[signals_key][-50:]
# Trading logic
if rsi < 30 and portfolio.position(symbol) == 0:
orders.append({
"symbol": symbol,
"action": "buy",
"quantity": 50
})
state[last_trade_key] = state['current_timestamp']
return orders if orders else NoneAdvanced Patterns
State-Based Strategy Modes
python
def on_strategy_start(portfolio, state):
"""Initialize strategy mode"""
state['strategy_mode'] = 'accumulation' # accumulation, trending, defensive
state['mode_switch_date'] = None
def on_bar_close(data_contexts, portfolio, state):
"""Adapt strategy based on market conditions"""
aapl = data_contexts['AAPL']
# Switch modes based on volatility
atr = aapl.atr(20)
volatility = atr / aapl.close
current_mode = state['strategy_mode']
new_mode = current_mode
if volatility > 0.03: # High volatility
new_mode = 'defensive'
elif volatility < 0.01: # Low volatility
new_mode = 'accumulation'
else:
new_mode = 'trending'
# Mode switch logic
if new_mode != current_mode:
print(f"Switching from {current_mode} to {new_mode} mode")
state['strategy_mode'] = new_mode
state['mode_switch_date'] = state['current_timestamp']
# Execute different strategies based on mode
if state['strategy_mode'] == 'accumulation':
return accumulation_strategy(aapl, portfolio, state)
elif state['strategy_mode'] == 'trending':
return trending_strategy(aapl, portfolio, state)
else: # defensive
return defensive_strategy(aapl, portfolio, state)Rolling Window Calculations
python
def on_bar_close(data_contexts, portfolio, state):
"""Maintain rolling calculations"""
aapl = data_contexts['AAPL']
# Initialize rolling window
if 'price_window' not in state:
state['price_window'] = []
state['volume_window'] = []
# Add current data
state['price_window'].append(aapl.close)
state['volume_window'].append(aapl.volume)
# Maintain window size (last 100 bars)
window_size = 100
if len(state['price_window']) > window_size:
state['price_window'] = state['price_window'][-window_size:]
state['volume_window'] = state['volume_window'][-window_size:]
# Calculate custom metrics
if len(state['price_window']) >= 20:
# Custom volatility calculation
prices = state['price_window'][-20:]
returns = [(prices[i] - prices[i-1])/prices[i-1] for i in range(1, len(prices))]
custom_volatility = (sum(r**2 for r in returns) / len(returns)) ** 0.5
state['custom_volatility'] = custom_volatility
# Use in trading decision
if custom_volatility < 0.01: # Low volatility
return {"symbol": "AAPL", "action": "buy", "quantity": 100}Best Practices
Memory Management
python
def on_bar_close(data_contexts, portfolio, state):
"""Manage state memory efficiently"""
# Limit list sizes
max_history = 1000
for key in state:
if isinstance(state[key], list) and len(state[key]) > max_history:
state[key] = state[key][-max_history:] # Keep recent data only
# Clean up old entries
current_time = state['current_timestamp']
cutoff_time = current_time - timedelta(days=30)
# Remove entries older than 30 days
keys_to_clean = [k for k in state.keys() if k.endswith('_history')]
for key in keys_to_clean:
if isinstance(state[key], dict):
state[key] = {k: v for k, v in state[key].items()
if k >= cutoff_time}Safe State Access
python
def on_bar_close(data_contexts, portfolio, state):
"""Safe state access patterns"""
# Use get() with defaults
trade_count = state.get('trade_count', 0)
last_signal = state.get('last_signal', None)
# Initialize if missing
if 'initialized' not in state:
state['initialized'] = True
state['start_time'] = state['current_timestamp']
state['counters'] = {'buys': 0, 'sells': 0}
# Increment counters safely
state['counters']['buys'] = state['counters'].get('buys', 0) + 1Strategy Global Variables
The backtesting engine fully supports strategy-level global variables. You can define configuration variables, constants, and parameters directly in your strategy code, making your strategies self-contained and easy to understand.
Global Variable Support
python
# Strategy-specific globals - these work perfectly!
g_fast_period = 12
g_slow_period = 26
g_position_size = 500
g_risk_per_trade = 0.02
# Configuration constants
DEFAULT_SYMBOLS = ["AAPL", "GOOGL", "MSFT"]
MAX_POSITIONS = 3
TRADING_ENABLED = True
def on_strategy_start(portfolio, state):
"""Global variables are accessible in all lifecycle functions"""
print(f"Strategy configured with SMA periods: {g_fast_period}/{g_slow_period}")
print(f"Position size: {g_position_size}, Risk per trade: {g_risk_per_trade}")
# Store in state if needed for later access
state['fast_period'] = g_fast_period
state['slow_period'] = g_slow_period
state['max_positions'] = MAX_POSITIONS
def on_bar_close(data_contexts, portfolio, state):
"""Global variables work in all functions"""
if not TRADING_ENABLED:
return None
# Direct access to globals
for symbol in DEFAULT_SYMBOLS:
if symbol in data_contexts:
fast_sma = data_contexts[symbol].sma(g_fast_period)
slow_sma = data_contexts[symbol].sma(g_slow_period)
# ... trading logic using global variablesNaming Conventions
While any variable names work, these conventions are recommended:
python
# Recommended: Use descriptive prefixes
g_fast_period = 20 # 'g_' prefix for global parameters
DEFAULT_SYMBOLS = ["AAPL"] # 'DEFAULT_' for fallback values
MAX_DRAWDOWN = 0.15 # ALL_CAPS for constants
trading_enabled = True # lowercase for simple flags
# All of these patterns work and are supported!Hybrid Architecture Pattern
For maximum flexibility, you can combine strategy globals with API parameter overrides:
python
# Strategy defaults - always available
g_fast_period = 20
g_slow_period = 50
g_symbols = ["AAPL"]
def on_strategy_start(portfolio, state):
"""Hybrid approach: globals + API overrides"""
# API parameters can override strategy defaults
fast_period = state.get('fast_period', g_fast_period) # API override or global
slow_period = state.get('slow_period', g_slow_period) # API override or global
symbols = state.get('symbols', g_symbols) # API override or global
print(f"Using periods: {fast_period}/{slow_period} for {symbols}")
# Store final configuration
state['final_fast_period'] = fast_period
state['final_slow_period'] = slow_period
state['final_symbols'] = symbols
def on_bar_close(data_contexts, portfolio, state):
"""Use either globals directly OR state values"""
# Option 1: Use globals directly (simpler)
fast_sma = data_contexts['AAPL'].sma(g_fast_period)
# Option 2: Use state values (more flexible for API overrides)
final_fast_period = state['final_fast_period']
fast_sma = data_contexts['AAPL'].sma(final_fast_period)
# Both approaches work - choose based on your needs!Benefits of Strategy Globals:
- ✅ Natural Python: Define variables the standard Python way
- ✅ Self-Contained: Strategy code includes all configuration
- ✅ Easy Testing: Change parameters by editing the strategy file
- ✅ Clear Intent: Configuration is visible at the top of your strategy
- ✅ API Compatible: Still works with external parameter injection
Important Notes
- Persistence: State persists across all function calls throughout the backtest
- Types: Can store any Python object (numbers, strings, lists, dictionaries)
- Memory: Be mindful of memory usage with large datasets
- Thread Safety: Not needed - backtest runs in single thread
- Initialization: Initialize variables in
on_strategy_startwhen possible - System Keys: Don't modify system-provided keys (minute_index, symbols, etc.)
- Configuration: Use
state.get()with defaults for robust parameter access
The state dictionary is your strategy's long-term memory - use it wisely to build sophisticated, stateful trading logic with flexible configuration.