I built TradeSight — a self-hosted Python platform for algorithmic paper trading that runs overnight strategy tournaments to evolve the best-performing configuration before market open.
Here's the part that actually surprised me: letting strategies compete and reproduce overnight produced a Sharpe ratio of 2.53 — vs 0.3–0.8 from static indicator configs I'd hardcoded.
The tournament idea
Most algo trading tutorials give you a fixed strategy: buy when RSI < 30, sell when RSI > 70. The problem is that works until it doesn't — markets shift, and static params go stale.
So instead of tuning a single strategy, TradeSight runs a tournament each night:
- Seed ~20 strategy variants with randomized indicator weights (RSI threshold, MACD fast/slow, Bollinger width, etc.)
- Backtest each on the past 90 days of data
- Score by Sharpe ratio (risk-adjusted return)
- Keep the top 50%, mutate their params, repeat for 10 generations
- Promote the winner to live paper trading at market open
This is basically a genetic algorithm on trading configs. Simple, but it works.
def run_tournament(historical_data, generations=10, population_size=20):
population = [random_strategy() for _ in range(population_size)]
for gen in range(generations):
scored = [(strategy, backtest_sharpe(strategy, historical_data))
for strategy in population]
scored.sort(key=lambda x: x[1], reverse=True)
# Keep top half
survivors = [s for s, _ in scored[:population_size // 2]]
# Mutate + crossover to refill
population = survivors + [
mutate(random.choice(survivors))
for _ in range(population_size // 2)
]
winner = max(scored, key=lambda x: x[1])
return winner[0]
What TradeSight includes
- 15+ technical indicators: RSI, MACD, Bollinger Bands, EMA, SMA, ADX, Stochastic, VWAP, and more
- Overnight tournament engine: evolves strategy configs while you sleep
- Circuit breakers: per-position stop-loss, daily drawdown limits, position sizing constraints
- Alpaca integration: connects to Alpaca's paper trading API — free to use, real market data
- Web dashboard: see active positions, P&L, tournament results, and signal history
- No subscription fees: fully self-hosted, runs on any machine with Python 3.9+
The circuit breaker design
One thing I spent a lot of time on: making sure a bad signal can't blow up the whole account. Every position has:
class CircuitBreaker:
def __init__(self, max_daily_loss_pct=0.02, max_position_pct=0.10):
self.max_daily_loss_pct = max_daily_loss_pct # 2% daily max loss
self.max_position_pct = max_position_pct # 10% max per position
def check(self, account_value, daily_pnl, proposed_position_value):
daily_loss_pct = abs(daily_pnl) / account_value
position_pct = proposed_position_value / account_value
if daily_loss_pct >= self.max_daily_loss_pct:
raise CircuitBreakerTripped(f"Daily loss limit hit: {daily_loss_pct:.1%}")
if position_pct > self.max_position_pct:
raise PositionTooLarge(f"Position {position_pct:.1%} exceeds limit")
A trading system without circuit breakers is an account-destroyer waiting to happen.
Getting started
git clone https://github.com/rmbell09-lang/tradesight
cd tradesight
pip install -r requirements.txt
cp .env.example .env # add your Alpaca paper trading keys
python app.py
You'll need a free Alpaca account for paper trading keys. No real money involved.
What I learned
Static strategies are a trap. The evolutionary approach consistently outperforms any single configuration I could tune by hand. The market isn't static — your strategy config shouldn't be either.
Position sizing matters more than signal quality. I had strategies with great win rates getting blown out by oversized positions on bad days. The circuit breakers fixed this.
Backtesting is necessary but not sufficient. A strategy that scores 2.53 Sharpe on 90 days of history doesn't mean it will keep that in live paper trading. The system is still evolving.
The repo is at github.com/rmbell09-lang/tradesight. It's MIT licensed, self-hosted, and free to use.
If you're interested in algo trading or building something similar, happy to answer questions in the comments.
Top comments (0)