Appearance
Writing Your First Trading Strategy
Learn to create a custom trading strategy from scratch. We'll build a simple but effective strategy that you can expand and customize.
Strategy Anatomy 101
Every strategy has three main components:
python
def strategy(data_contexts, portfolio, state):
# 1. Get market data
# 2. Make trading decision
# 3. Return order (or None)
return order
Let's break this down:
- data_contexts: Market prices and indicators for each symbol
- portfolio: Your account balance, positions, and performance
- state: Memory between function calls (optional)
Your First Strategy: Buy and Hold
Let's start with the simplest possible strategy:
python
def strategy(data_contexts, portfolio, state):
"""Buy and hold strategy - buy once and never sell"""
# Get Apple's data
aapl = data_contexts['AAPL']
# Check if we already own shares
current_position = portfolio.position('AAPL')
# If we don't own any, buy 100 shares
if current_position == 0:
return {
'symbol': 'AAPL',
'action': 'buy',
'quantity': 100
}
# Otherwise, do nothing
return None
What this does:
- Checks if we own AAPL shares
- If not, buys 100 shares
- Holds forever (never sells)
Level 2: Adding a Sell Condition
Let's add profit-taking logic:
python
def strategy(data_contexts, portfolio, state):
"""Buy low, sell high - take profits at 10% gain"""
aapl = data_contexts['AAPL']
current_position = portfolio.position('AAPL')
current_price = aapl.close
# Initialize state to track entry price
if 'entry_price' not in state:
state['entry_price'] = None
# Buy logic: if we don't have a position
if current_position == 0:
state['entry_price'] = current_price
return {
'symbol': 'AAPL',
'action': 'buy',
'quantity': 100
}
# Sell logic: if we made 10% profit
if current_position > 0 and state['entry_price']:
profit_percent = (current_price - state['entry_price']) / state['entry_price']
if profit_percent >= 0.10: # 10% profit
print(f"Taking profits! Gain: {profit_percent:.2%}")
state['entry_price'] = None
return {
'symbol': 'AAPL',
'action': 'sell',
'quantity': 'all'
}
return None
New concepts:
- Using
state
to remember our entry price - Calculating profit percentage
- Selling entire position with
quantity: 'all'
- Debug output with
print()
Level 3: Using Technical Indicators
Now let's use moving averages for smarter entries:
python
def strategy(data_contexts, portfolio, state):
"""Trend following - buy on uptrend, sell on downtrend"""
aapl = data_contexts['AAPL']
# Calculate indicators
sma_fast = aapl.sma(20) # 20-day moving average
sma_slow = aapl.sma(50) # 50-day moving average
current_price = aapl.close
# Skip if indicators aren't ready (need 50 days of data)
if sma_slow != sma_slow: # Check for NaN
return None
current_position = portfolio.position('AAPL')
# BUY SIGNAL: Fast MA crosses above Slow MA (uptrend starting)
if sma_fast > sma_slow and current_position == 0:
print(f"Uptrend detected! SMA20={sma_fast:.2f} > SMA50={sma_slow:.2f}")
return {
'symbol': 'AAPL',
'action': 'buy',
'quantity': 100
}
# SELL SIGNAL: Fast MA crosses below Slow MA (downtrend starting)
elif sma_fast < sma_slow and current_position > 0:
print(f"Downtrend detected! SMA20={sma_fast:.2f} < SMA50={sma_slow:.2f}")
return {
'symbol': 'AAPL',
'action': 'sell',
'quantity': 'all'
}
return None
Technical indicators available:
sma(period)
- Simple Moving Averageema(period)
- Exponential Moving Averagersi(period)
- Relative Strength Index (0-100)
Level 4: Risk Management
Add stop-loss to limit losses:
python
def strategy(data_contexts, portfolio, state):
"""Trend following with risk management"""
aapl = data_contexts['AAPL']
# Indicators
sma_20 = aapl.sma(20)
sma_50 = aapl.sma(50)
current_price = aapl.close
# Skip if indicators aren't ready
if sma_50 != sma_50:
return None
# Initialize state
if 'highest_price' not in state:
state['highest_price'] = 0
state['entry_price'] = 0
current_position = portfolio.position('AAPL')
# Entry logic
if sma_20 > sma_50 and current_position == 0:
state['entry_price'] = current_price
state['highest_price'] = current_price
return {
'symbol': 'AAPL',
'action': 'buy',
'quantity': 100
}
# Exit logic (when holding position)
if current_position > 0:
# Update highest price (for trailing stop)
state['highest_price'] = max(state['highest_price'], current_price)
# Stop loss: Exit if price drops 5% from entry
loss_from_entry = (current_price - state['entry_price']) / state['entry_price']
if loss_from_entry <= -0.05:
print(f"Stop loss triggered! Loss: {loss_from_entry:.2%}")
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': 'all'}
# Trailing stop: Exit if price drops 8% from highest
drop_from_high = (current_price - state['highest_price']) / state['highest_price']
if drop_from_high <= -0.08:
print(f"Trailing stop triggered! Drop from high: {drop_from_high:.2%}")
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': 'all'}
# Trend exit: Sell if trend reverses
if sma_20 < sma_50:
print("Trend reversal - exiting position")
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': 'all'}
return None
Risk management concepts:
- Stop Loss: Limit losses to 5% of entry price
- Trailing Stop: Lock in profits by selling if price drops 8% from peak
- Trend Exit: Sell when moving averages signal downtrend
Level 5: Position Sizing
Use portfolio percentage instead of fixed shares:
python
def strategy(data_contexts, portfolio, state):
"""Dynamic position sizing based on portfolio value"""
aapl = data_contexts['AAPL']
# Indicators
sma_20 = aapl.sma(20)
rsi = aapl.rsi(14)
current_price = aapl.close
# Skip if indicators aren't ready
if sma_20 != sma_20 or rsi != rsi:
return None
current_position = portfolio.position('AAPL')
# Buy signal: Uptrend + Not overbought
if current_position == 0:
if sma_20 > aapl.sma(50) and rsi < 70:
# Calculate position size as 20% of portfolio
position_value = portfolio.equity * 0.20
shares_to_buy = int(position_value / current_price)
if shares_to_buy > 0:
print(f"Buying {shares_to_buy} shares (20% of portfolio)")
return {
'symbol': 'AAPL',
'action': 'buy',
'quantity': shares_to_buy
}
# Sell signal: Overbought or downtrend
elif current_position > 0:
if rsi > 80 or sma_20 < aapl.sma(50):
reason = "RSI overbought" if rsi > 80 else "Trend reversal"
print(f"Selling: {reason}")
return {
'symbol': 'AAPL',
'action': 'sell',
'quantity': 'all'
}
return None
Complete Example: Momentum Strategy
Here's a complete strategy combining everything we've learned:
python
def strategy(data_contexts, portfolio, state):
"""
Momentum Trading Strategy
- Entry: SMA crossover + RSI confirmation
- Exit: Stop loss, trailing stop, or signal reversal
- Position sizing: Risk 2% per trade
"""
# Configuration
RISK_PER_TRADE = 0.02 # Risk 2% of portfolio per trade
STOP_LOSS_PCT = 0.05 # 5% stop loss
TRAILING_STOP_PCT = 0.08 # 8% trailing stop
# Get data
aapl = data_contexts['AAPL']
# Calculate indicators
sma_fast = aapl.sma(10)
sma_slow = aapl.sma(30)
rsi = aapl.rsi(14)
current_price = aapl.close
volume = aapl.volume
avg_volume = sum(aapl.history('volume', 20)) / 20
# Check if indicators are ready
if sma_slow != sma_slow or rsi != rsi:
return None
# Initialize state
if 'trades_count' not in state:
state['trades_count'] = 0
state['entry_price'] = 0
state['highest_price'] = 0
current_position = portfolio.position('AAPL')
# ENTRY LOGIC
if current_position == 0:
# Entry conditions:
# 1. Moving average crossover (trend)
# 2. RSI not overbought (momentum)
# 3. Volume above average (confirmation)
trend_signal = sma_fast > sma_slow
momentum_signal = 30 < rsi < 70
volume_signal = volume > avg_volume
if trend_signal and momentum_signal and volume_signal:
# Calculate position size based on risk
account_risk = portfolio.equity * RISK_PER_TRADE
risk_per_share = current_price * STOP_LOSS_PCT
shares_to_buy = int(account_risk / risk_per_share)
# Ensure we don't use more than 30% of portfolio
max_shares = int((portfolio.equity * 0.30) / current_price)
shares_to_buy = min(shares_to_buy, max_shares)
if shares_to_buy > 0 and portfolio.cash > shares_to_buy * current_price:
state['entry_price'] = current_price
state['highest_price'] = current_price
state['trades_count'] += 1
print(f"Trade #{state['trades_count']}: BUY {shares_to_buy} shares at ${current_price:.2f}")
print(f" Signals: SMA={sma_fast:.2f}/{sma_slow:.2f}, RSI={rsi:.1f}, Vol={volume/avg_volume:.1f}x")
return {
'symbol': 'AAPL',
'action': 'buy',
'quantity': shares_to_buy
}
# EXIT LOGIC
elif current_position > 0:
# Update trailing stop
state['highest_price'] = max(state['highest_price'], current_price)
# Calculate performance
unrealized_pnl = (current_price - state['entry_price']) / state['entry_price']
drop_from_high = (current_price - state['highest_price']) / state['highest_price']
# Exit conditions (in priority order):
# 1. Stop Loss
if unrealized_pnl <= -STOP_LOSS_PCT:
print(f"STOP LOSS: Exiting at ${current_price:.2f} (Loss: {unrealized_pnl:.2%})")
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': 'all'}
# 2. Trailing Stop
if drop_from_high <= -TRAILING_STOP_PCT and unrealized_pnl > 0:
print(f"TRAILING STOP: Locking profits at ${current_price:.2f} (Gain: {unrealized_pnl:.2%})")
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': 'all'}
# 3. Signal Reversal
if sma_fast < sma_slow or rsi > 80:
reason = "Overbought (RSI>80)" if rsi > 80 else "Trend reversal"
print(f"SIGNAL EXIT ({reason}): Selling at ${current_price:.2f} (P&L: {unrealized_pnl:.2%})")
return {'symbol': 'AAPL', 'action': 'sell', 'quantity': 'all'}
return None
Testing Your Strategy
- Copy the code into the strategy editor
- Click Run Backtest or press Shift+Enter
- Check Debug Output for your print statements
- Analyze Results:
- Did it make money?
- How many trades?
- What was the max drawdown?
Common Patterns to Try
Pattern 1: Mean Reversion
Buy when RSI < 30 (oversold), sell when RSI > 70 (overbought)
Pattern 2: Breakout
Buy when price breaks above 20-day high, sell at 10-day low
Pattern 3: Volume Surge
Buy when volume is 2x average and price is rising
Debugging Tips
Add print statements to understand your strategy:
python
print(f"Price: ${current_price:.2f}, SMA: {sma:.2f}, RSI: {rsi:.1f}")
print(f"Position: {current_position} shares, Cash: ${portfolio.cash:.2f}")
print(f"Signal triggered: Buying {shares} shares")
Next Steps
You're ready to:
- Learn Advanced Order Types
- Master Technical Indicators
- Explore Example Strategies
- Understand Risk Management
Remember: Backtesting shows historical performance, not future results. Always test thoroughly before using real money!