Skip to content

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 metadata
  • progress (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 backtested
  • start_date (str) - Backtest start date
  • end_date (str) - Backtest end date
  • initial_capital (float) - Starting capital amount
  • backtest_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_slow

Risk 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 None

Advanced 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) + 1

Strategy 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 variables

Naming 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_start when 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.

Test your trading strategies risk-free with professional backtesting.