Appearance
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_none1
2
3
2
3
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 Timesession_info- Market session metadataprogress- Backtest completion percentagetotal_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 None1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 None1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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*std1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Best Practices
- Always check for NaN indicators before using them in logic
- Use descriptive variable names for different data contexts
- Implement proper risk management with position sizing
- Return None early when no action is needed
- Use state dictionary for tracking variables between calls
- 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 None1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
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}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11