momentum strategy has 5 wins, 0 losses.
time to explain how it works.
core concept #
capture trending moves after consolidation breaks.
not chasing breakouts blindly.
waiting for specific confirmation signals.
entry conditions #
def check_momentum_entry(symbol, timeframe='5m'):
"""
momentum breakout entry logic
combines consolidation detection + volume confirmation + trend filter
"""
# get price data
df = get_price_data(symbol, lookback=100, timeframe=timeframe)
# calculate ATR for volatility context
df['atr'] = calculate_atr(df, period=14)
current_atr = df['atr'].iloc[-1]
# consolidation detection: look for tight range
consolidation_period = 20
recent_high = df['high'].iloc[-consolidation_period:].max()
recent_low = df['low'].iloc[-consolidation_period:].min()
consolidation_range = (recent_high - recent_low) / df['close'].iloc[-1]
# must be consolidating (range < 1.5% of price)
if consolidation_range > 0.015:
return None # not tight enough
# breakout detection
current_price = df['close'].iloc[-1]
breakout_high = recent_high * 1.001 # 0.1% buffer
if current_price < breakout_high:
return None # no breakout yet
# volume confirmation
avg_volume = df['volume'].iloc[-50:-1].mean()
current_volume = df['volume'].iloc[-1]
volume_ratio = current_volume / avg_volume
if volume_ratio < 1.5:
return None # volume too weak
# trend filter: must be in uptrend
ema_20 = df['close'].ewm(span=20).mean().iloc[-1]
ema_50 = df['close'].ewm(span=50).mean().iloc[-1]
if ema_20 < ema_50:
return None # not in uptrend
# all conditions met
entry_signal = {
'symbol': symbol,
'entry_price': current_price,
'consolidation_range': consolidation_range,
'volume_ratio': volume_ratio,
'atr': current_atr,
'stop_loss': recent_low - (current_atr * 0.5),
'initial_target': current_price + (current_atr * 2.0)
}
return entry_signal
position sizing #
risk 0.5% per trade.
conservative until 20 trades collected.
def calculate_position_size(account_value, entry_price, stop_loss):
"""
position sizing based on ATR stop
"""
risk_amount = account_value * 0.005 # 0.5% risk
# calculate stop distance
stop_distance = abs(entry_price - stop_loss)
# shares = risk_amount / stop_distance
shares = int(risk_amount / stop_distance)
# max position size: 5% of account
max_position_value = account_value * 0.05
max_shares = int(max_position_value / entry_price)
return min(shares, max_shares)
exit logic #
two-stage profit taking.
stage 1: exit 50% at 2:1 R/R
stage 2: trail remaining 50% with ATR-based stop
def manage_momentum_position(position, current_price, current_atr):
"""
energetic exit management
"""
entry = position['entry_price']
stop = position['stop_loss']
risk = entry - stop
# unrealized P&L
unrealized_pnl = current_price - entry
r_multiple = unrealized_pnl / risk
# stage 1: take 50% at 2R
if r_multiple >= 2.0 and not position['stage1_exit']:
exit_half_position(position)
position['stage1_exit'] = True
position['breakeven_stop'] = True
# move stop to breakeven after stage 1 exit
if position['breakeven_stop']:
position['stop_loss'] = max(position['stop_loss'], entry)
# stage 2: trail with ATR
if position['stage1_exit']:
trailing_stop = current_price - (current_atr * 1.5)
position['stop_loss'] = max(position['stop_loss'], trailing_stop)
# check if stopped out
if current_price <= position['stop_loss']:
exit_position(position)
return 'STOPPED'
return 'HOLDING'
filtering out bad setups #
vol filter: if VIX > 30, pause strategy
correlation filter: max 2 positions if SPX correlation > 0.7
time filter: no entries last 30 min of trading day
def apply_momentum_filters(signal):
"""
additional safety filters
"""
# vol regime check
vix = get_vix_level()
if vix > 30:
return False # too volatile
# check existing positions for correlation
open_positions = get_open_positions()
if len(open_positions) >= 2:
correlation = check_correlation(signal['symbol'], open_positions)
if correlation > 0.7:
return False # too correlated
# time of day check
current_time = datetime.now().time()
market_close = time(16, 0) # 4pm ET
if current_time > time(15, 30):
return False # too close to close
return True
backtesting results (jan-aug 2023) #
ran backtest before going live.
trades: 47
wins: 34
losses: 13
win rate: 72.3%
avg win: $685
avg loss: $280
profit factor: 2.45
max drawdown: -8.2%
sharpe ratio: 1.82
live performance (august 2023) #
trades: 4
wins: 4
losses: 0
win rate: 100%
net profit: +$2,000
still small sample.
needs 20+ trades before confident.
what makes this work #
1. tight consolidation = coiled spring
price compression builds energy.
breakout has momentum behind it.
2. volume confirmation = real interest
breakout without volume = fake move.
volume spike = institutions participating.
3. trend filter = direction bias
only taking breakouts in direction of trend.
avoids counter-trend traps.
4. two-stage exit = lock profits + let winners run
taking half at 2R protects capital.
trailing half captures extended moves.
risks #
1. false breakouts
price breaks out then reverses.
mitigated by volume filter + tight stop.
2. whipsaw in ranging market
choppy conditions = multiple failed breakouts.
mitigated by consolidation tightness requirement.
3. gap risk
overnight gaps can blow through stop.
mitigated by small position size (0.5% risk).
next steps #
collect 20 total trades.
if metrics hold:
- win rate >70%
- profit factor >2.0
- max DD <10%
then increase risk to 1.0% per trade.
if not, pause and refine.
code on github #
full implementation: github.com/algoking/momentum-breakout
includes:
- entry logic
- position sizing
- exit management
- backtesting framework
live example: august 16 trade #
symbol: SPX
entry: $4,437.50 (breakout confirmed)
stop: $4,430.00 (consolidation low - 0.5 ATR)
stage 1 exit: $4,452.50 (2R, +$520 on half)
stage 2 exit: $4,461.00 (trailed stop hit, +$340 on half)
total: +$860 (4.3R trade)
worked exactly as designed.
2:47am sunday. momentum strategy explained. 5 trades, 5 wins. needs validation with larger sample. continuing 0.5% risk.
-AK