I've been running automated paper trading for a while now, and one thing I kept running into: which strategy is actually performing best right now? Not last month. Not in a backtest from 3 years ago. Right now.
The answer I kept giving myself was "I'll check the logs." The logs were a mess. So I built a strategy ranking system into TradeSight — my self-hosted Python trading lab — and it changed how I think about algorithmic trading entirely.
The Problem with "Which Strategy Is Better?"
Most algo trading systems commit to one strategy. You pick RSI crossover, you backtest it, you deploy it. Done.
The problem: market regimes change. What works in a trending market (EMA crossovers crush it) completely falls apart in a choppy sideways market (RSI oscillators do better). No single strategy dominates all conditions.
So instead of picking one, I run them all simultaneously and let performance decide.
How the Tournament System Works
TradeSight runs what I call overnight strategy tournaments. Here's the flow:
- During market hours: All active strategies generate signals and track paper trades via Alpaca
- After market close: Each strategy's performance is scored
- Overnight: A tournament runs — lowest performers get retired, new variants get generated
- Next morning: Updated strategy lineup, ready to trade
The scoring uses a composite metric:
def score_strategy(strategy, lookback_days=14):
trades = get_recent_trades(strategy.id, days=lookback_days)
if len(trades) < MIN_TRADES:
return 0.0 # not enough data
win_rate = sum(1 for t in trades if t.pnl > 0) / len(trades)
avg_pnl = sum(t.pnl for t in trades) / len(trades)
max_drawdown = calculate_drawdown(trades)
# Weighted composite score
score = (win_rate * 0.4) + (normalize(avg_pnl) * 0.4) + (normalize(-max_drawdown) * 0.2)
return score
The 40/40/20 weighting (win rate, avg P&L, drawdown) is tunable. I've found this split works well for short-term overnight strategies.
The 5 Strategies Currently in Rotation
Here's what's actually running in my current tournament:
1. RSI Mean Reversion
Classic oversold/overbought detection. Buys when RSI < 30, sells when RSI > 70.
def rsi_signal(prices, period=14):
rsi = calculate_rsi(prices, period)
if rsi[-1] < 30:
return Signal.BUY
elif rsi[-1] > 70:
return Signal.SELL
return Signal.HOLD
Best in: sideways/choppy markets
Weakness: kills you in strong trends
2. EMA Crossover
Fast EMA (9-period) crossing slow EMA (21-period). Golden cross = buy. Death cross = sell.
Best in: trending markets
Weakness: whipsaws in choppy conditions
3. Bollinger Band Squeeze
Detects volatility contractions. When bands narrow and price breaks out, follow the breakout.
Best in: pre-earnings setups, range breakouts
Weakness: false breakouts are painful
4. MACD Histogram Reversal
Watches for MACD histogram momentum shifts, not just line crossovers.
Best in: medium-term momentum plays
Weakness: lags in fast-moving conditions
5. Confluence Strategy (the experiment)
Requires 3+ indicators to agree before entering a trade. Higher selectivity, fewer trades.
def confluence_signal(prices):
signals = [
rsi_signal(prices),
ema_signal(prices),
macd_signal(prices),
volume_signal(prices),
]
buys = sum(1 for s in signals if s == Signal.BUY)
sells = sum(1 for s in signals if s == Signal.SELL)
if buys >= 3:
return Signal.BUY
elif sells >= 3:
return Signal.SELL
return Signal.HOLD
Best in: any market — but generates fewer signals
Weakness: can miss fast moves
What the Tournament Data Actually Shows
After running this for several weeks, the results surprised me:
- RSI tops the leaderboard most often (choppy markets are more common than trends)
- Confluence has the highest win rate but lowest trade frequency — it's picky
- EMA crossover gets destroyed in choppy weeks, then shoots to the top during trending weeks
- MACD is consistently middle-of-the-pack — never terrible, never great
The most useful insight: no strategy should be trusted with more than 25-30% of capital allocation. Diversifying across strategies that aren't correlated reduces drawdown significantly.
Adding It to Your Own Trading Bot
If you want to implement a similar tournament in Python:
class StrategyTournament:
def __init__(self, strategies, capital=10000):
self.strategies = strategies
self.capital = capital
self.allocations = {s.id: capital / len(strategies) for s in strategies}
def rebalance(self):
"""Called after market close — redistributes capital based on performance."""
scores = {s.id: s.score() for s in self.strategies}
total_score = sum(scores.values())
if total_score == 0:
return # all strategies performed equally poorly, keep even split
for strategy in self.strategies:
weight = scores[strategy.id] / total_score
self.allocations[strategy.id] = self.capital * weight
def get_allocation(self, strategy_id):
return self.allocations.get(strategy_id, 0)
The key insight: let performance dynamically adjust capital allocation. Better-performing strategies get more capital, worse performers get less, everything gets a shot to recover.
The Live Dashboard
TradeSight has a web dashboard (Flask) that shows the current tournament standings in real time:
- Current score for each active strategy
- Trade history per strategy
- P&L breakdown
- Live paper trade positions
If you want to run the same setup: TradeSight is on GitHub — MIT licensed, fully self-hosted, connects to Alpaca paper trading (free account).
What I Learned
The biggest lesson from building this: most traders lose because they pick one strategy and stick with it through conditions where it doesn't work. Systematic rotation based on recent performance isn't a magic bullet, but it's a lot better than gut-feel switching.
The tournament system also makes it easy to test new strategies without risk — just add them to the rotation with a small allocation and watch how they perform in live conditions before committing more capital.
TradeSight is open source: github.com/rmbell09-lang/tradesight
Running on Python 3.11+, Flask, Alpaca Markets API. Paper trading only — not financial advice.
Top comments (0)