Skip to content

on_market_close

Called at 4:00 PM ET each trading day when the market closes. Use this for end-of-day analysis, position management, or daily reporting.

Signature

python
def on_market_close(portfolio, state, market_data):
    # Daily market close logic
    pass

Parameters

  • portfolio (PortfolioContext) - Current portfolio state
  • state (dict) - Strategy state dictionary
  • market_data (dict) - Market session information containing:
    • date - Trading date
    • timestamp - Market close timestamp
    • is_market_close - Always True for this function
    • trading_day - Current trading day
    • session_time - Time of day (4:00 PM ET)

Usage

This function is optional but useful for end-of-day tasks and daily analysis.

Common Use Cases

  • Calculate daily P&L and performance
  • Close or adjust positions before overnight
  • Log daily trading summary
  • Update risk management parameters
  • Save closing prices for gap analysis

Examples

Basic Daily Summary

python
def on_market_close(portfolio, state, market_data):
    """Log daily trading summary"""
    trading_date = market_data['date']
    
    # Calculate daily P&L
    if 'today_start_equity' in state:
        daily_pnl = portfolio.equity - state['today_start_equity']
        daily_return = daily_pnl / state['today_start_equity']
        
        print(f"Market closed - {trading_date}")
        print(f"Daily P&L: ${daily_pnl:,.2f} ({daily_return*100:.2f}%)")
    
    # Log position summary
    positions = portfolio.positions()
    if positions:
        print(f"Overnight positions: {positions}")
    else:
        print("No overnight positions")
    
    # Update state for next day
    state['previous_close_equity'] = portfolio.equity

Position Management

python
def on_market_close(portfolio, state, market_data):
    """Manage positions at market close"""
    trading_date = market_data['date']
    
    # Close all positions on Friday (no weekend exposure)
    if market_data['timestamp'].weekday() == 4:  # Friday
        positions = portfolio.positions()
        if positions:
            print(f"Closing all positions for weekend - {len(positions)} symbols")
            state['weekend_close_needed'] = True
            
            # Flag positions to close (will be handled in next on_bar_close)
            for symbol in positions:
                state[f'close_{symbol}'] = True
    
    # Risk management - close losing positions
    for symbol, shares in portfolio.positions().items():
        if shares > 0:  # Only check long positions
            unrealized_pnl = portfolio.unrealized_pnl(symbol)
            position_value = portfolio.market_value(symbol)
            
            # Close positions with >10% loss
            if unrealized_pnl < -0.10 * position_value:
                print(f"Marking {symbol} for closure - Large loss: ${unrealized_pnl:.2f}")
                state[f'close_{symbol}'] = True

Performance Analysis

python
def on_market_close(portfolio, state, market_data):
    """Detailed daily performance analysis"""
    trading_date = market_data['date']
    
    # Calculate comprehensive daily metrics
    start_equity = state.get('today_start_equity', portfolio.initial_capital)
    daily_pnl = portfolio.equity - start_equity
    daily_return = daily_pnl / start_equity
    
    # Track high/low for the day
    state['daily_high'] = max(state.get('daily_high', portfolio.equity), portfolio.equity)
    state['daily_low'] = min(state.get('daily_low', portfolio.equity), portfolio.equity)
    
    # Calculate drawdown
    peak_equity = state.get('peak_equity', portfolio.initial_capital)
    current_drawdown = (peak_equity - portfolio.equity) / peak_equity
    
    # Update peak if new high
    if portfolio.equity > peak_equity:
        state['peak_equity'] = portfolio.equity
        current_drawdown = 0
    
    # Win/loss tracking
    if 'win_days' not in state:
        state['win_days'] = 0
        state['loss_days'] = 0
    
    if daily_pnl > 0:
        state['win_days'] += 1
    elif daily_pnl < 0:
        state['loss_days'] += 1
    
    # Log comprehensive summary
    total_days = state['win_days'] + state['loss_days']
    win_rate = state['win_days'] / total_days if total_days > 0 else 0
    
    print(f"=== Daily Close Summary - {trading_date} ===")
    print(f"Daily P&L: ${daily_pnl:,.2f} ({daily_return*100:.2f}%)")
    print(f"Total Equity: ${portfolio.equity:,.2f}")
    print(f"Current Drawdown: {current_drawdown*100:.2f}%")
    print(f"Win Rate: {win_rate*100:.1f}% ({state['win_days']}/{total_days})")

Save Market Data for Analysis

python
def on_market_close(portfolio, state, market_data):
    """Save closing data for next day's analysis"""
    trading_date = market_data['date']
    
    # Save closing prices for gap analysis
    if 'previous_closes' not in state:
        state['previous_closes'] = {}
    
    # This would need to be integrated with data_contexts
    # In practice, you'd save this from the last on_bar_close call
    # state['previous_closes'][symbol] = last_close_price
    
    # Save portfolio metrics
    state['daily_equity_history'] = state.get('daily_equity_history', [])
    state['daily_equity_history'].append({
        'date': trading_date,
        'equity': portfolio.equity,
        'return': (portfolio.equity - portfolio.initial_capital) / portfolio.initial_capital
    })
    
    # Clean up old data (keep last 30 days)
    if len(state['daily_equity_history']) > 30:
        state['daily_equity_history'] = state['daily_equity_history'][-30:]

Risk Management Updates

python
def on_market_close(portfolio, state, market_data):
    """Update risk management parameters daily"""
    trading_date = market_data['date']
    
    # Calculate portfolio volatility (last 10 days)
    if 'daily_returns' in state and len(state['daily_returns']) >= 10:
        recent_returns = state['daily_returns'][-10:]
        avg_return = sum(recent_returns) / len(recent_returns)
        volatility = (sum((r - avg_return)**2 for r in recent_returns) / len(recent_returns)) ** 0.5
        
        # Adjust position sizing based on volatility
        if volatility > 0.02:  # High volatility (>2% daily)
            state['position_multiplier'] = 0.5  # Reduce position sizes
            print(f"High volatility detected: {volatility*100:.1f}% - Reducing position sizes")
        else:
            state['position_multiplier'] = 1.0  # Normal position sizes
    
    # Update stop loss levels based on portfolio size
    state['stop_loss_pct'] = max(0.02, 1000 / portfolio.equity)  # Min 2% or $1000
    
    print(f"Updated risk parameters - Stop loss: {state['stop_loss_pct']*100:.1f}%")

Integration Patterns

Flag-based Position Closing

python
def on_market_close(portfolio, state, market_data):
    """Set flags for position management"""
    # Mark positions to close tomorrow morning
    state['close_all_positions'] = should_close_all_positions()
    
def on_bar_close(data_contexts, portfolio, state):
    """Handle close flags"""
    # Check for close flags from market close
    if state.get('close_all_positions', False):
        positions = portfolio.positions()
        if positions:
            orders = []
            for symbol, shares in positions.items():
                orders.append({
                    "symbol": symbol,
                    "action": "sell",
                    "quantity": "all"
                })
            
            # Clear the flag
            state['close_all_positions'] = False
            return orders
    
    # Regular trading logic...

Notes

  • This function runs once per trading day at market close (4:00 PM ET)
  • Perfect for daily analysis and end-of-day position management
  • Use this to prepare for overnight exposure decisions
  • All timestamps are in Eastern Time (NYSE timezone)
  • This is not called on weekends or holidays
  • Any orders returned here won't execute (market is closed)

Test your trading strategies risk-free with professional backtesting.