Skip to content

on_bar_close

The main trading function called every minute after each bar completes. This is where your core trading logic lives.

Signature

python
def on_bar_close(data_contexts, portfolio, state):
    # Your trading logic here
    return orders_or_none

Parameters

  • data_contexts (dict[str, DataContext]) - Market data by symbol (e.g., {'AAPL': DataContext, 'GOOGL': DataContext})
  • portfolio (PortfolioContext) - Current portfolio state with positions, cash, and performance
  • state (dict) - Strategy state with custom data plus system info:
    • minute_index - Current minute (0-based)
    • current_timestamp - Bar timestamp in Eastern Time
    • session_info - Market session metadata
    • progress - Backtest completion percentage
    • total_minutes - Total minutes in backtest

Returns

  • dict - Single order: {"symbol": "AAPL", "action": "buy", "quantity": 100}
  • list - Multiple orders: [order1, order2, order3]
  • None - No action this period

Usage

This function is required for all strategies. It replaces the old strategy function.

Order Execution Timing

Orders returned from on_bar_close execute at the next bar's open price (realistic timing).

Examples

Basic Moving Average Crossover

python
def on_bar_close(data_contexts, portfolio, state):
    """Simple SMA crossover strategy"""
    aapl = data_contexts['AAPL']
    
    # Calculate indicators
    sma_20 = aapl.sma(20)
    sma_50 = aapl.sma(50)
    
    # Skip if indicators not ready
    if sma_20 != sma_20 or sma_50 != sma_50:  # Check for NaN
        return None
    
    current_position = portfolio.position('AAPL')
    
    # Buy signal: SMA(20) crosses above SMA(50)
    if sma_20 > sma_50 and current_position == 0:
        return {"symbol": "AAPL", "action": "buy", "quantity": 100}
    
    # Sell signal: SMA(20) crosses below SMA(50)
    if sma_20 < sma_50 and current_position > 0:
        return {"symbol": "AAPL", "action": "sell", "quantity": "all"}
    
    return None

Multi-Asset Strategy

python
def on_bar_close(data_contexts, portfolio, state):
    """Trade multiple symbols with RSI"""
    orders = []
    
    for symbol, data in data_contexts.items():
        rsi = data.rsi(14)
        
        # Skip if RSI not ready
        if rsi != rsi:  # Check for NaN
            continue
            
        position = portfolio.position(symbol)
        
        # Oversold - buy signal
        if rsi < 30 and position == 0:
            orders.append({
                "symbol": symbol,
                "action": "buy", 
                "quantity": 50
            })
        
        # Overbought - sell signal
        elif rsi > 70 and position > 0:
            orders.append({
                "symbol": symbol,
                "action": "sell",
                "quantity": "all"
            })
    
    return orders if orders else None

Advanced with State Management

python
def on_bar_close(data_contexts, portfolio, state):
    """Strategy with state tracking and risk management"""
    aapl = data_contexts['AAPL']
    minute = state['minute_index']
    
    # Update state tracking
    state['current_price'] = aapl.close
    
    # Risk check - daily loss limit
    if state.get('daily_pnl', 0) < -state.get('daily_loss_limit', 0):
        return None  # Stop trading for the day
    
    # Technical analysis
    rsi = aapl.rsi(14)
    bb_upper, bb_lower = calculate_bollinger_bands(aapl)
    
    if rsi < 30 and aapl.close < bb_lower:
        # Strong oversold signal
        position_size = min(100, portfolio.shares_for_dollars('AAPL', 10000))
        
        return {
            "symbol": "AAPL",
            "action": "buy", 
            "quantity": position_size
        }
    
    return None

def calculate_bollinger_bands(data):
    """Helper function for Bollinger Bands"""
    prices = data.history('close', 20)
    if len(prices) < 20:
        return None, None
    
    sma = sum(prices) / len(prices)
    std = (sum((p - sma)**2 for p in prices) / len(prices)) ** 0.5
    
    return sma + 2*std, sma - 2*std

Best Practices

  1. Always check for NaN indicators before using them in logic
  2. Use descriptive variable names for different data contexts
  3. Implement proper risk management with position sizing
  4. Return None early when no action is needed
  5. Use state dictionary for tracking variables between calls
  6. Test with simple logic first before adding complexity

Common Patterns

Error Handling

python
def on_bar_close(data_contexts, portfolio, state):
    try:
        aapl = data_contexts.get('AAPL')
        if not aapl:
            return None
            
        # Your logic here...
        
    except Exception as e:
        print(f"Error in on_bar_close: {e}")
        return None

Position Sizing

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

# Percentage of portfolio
target_value = portfolio.equity * 0.05  # 5% of portfolio
quantity = portfolio.shares_for_dollars('AAPL', target_value)
return {"symbol": "AAPL", "action": "buy", "quantity": quantity}

# All available cash
max_shares = portfolio.shares_for_dollars('AAPL', portfolio.cash)
return {"symbol": "AAPL", "action": "buy", "quantity": max_shares}

Test your trading strategies risk-free with professional backtesting.