Why Did Atlas-GIC's AI Trading System Underperform the S&P500?
I spent 18 months building a sophisticated 25-agent AI trading system. It generated a respectable +22% return over 378 trading days. But when I compared it to the S&P500’s +24% return over the same period, I had to face an uncomfortable truth: all that complexity underperformed a simple buy-and-hold strategy.
This is the postmortem of what went wrong and the hard lessons I learned about optimizing for the wrong metrics.
The Uncomfortable Numbers
Let me start with the raw comparison:
┌─────────────────────────────────────────────────────────────┐│ PERFORMANCE SUMMARY │├─────────────────────────────────────────────────────────────┤│ AI Trading System (25 agents) ││ • Total Return: +22% ││ • Period: 378 trading days (~18 months) ││ • Trading Activity: Constant daily trades ││ • Computational Cost: Significant API tokens ││ ││ S&P500 Index (Buy and Hold) ││ • Total Return: +24% ││ • Period: Same 378 trading days ││ • Trading Activity: 0 trades (buy once, hold) ││ • Computational Cost: $0 │├─────────────────────────────────────────────────────────────┤│ VERDICT: AI system underperformed by 2 percentage points ││ despite massive complexity │└─────────────────────────────────────────────────────────────┘The Reddit comment that stung the most:
“Despite all of these tokens spent, it does not appear to have gained a better total return than the index”
They were right. I had optimized for the wrong metric.
The Sharpe Ratio Trap
Here’s where I went wrong. I had been optimizing for Sharpe ratio, not total return.
import numpy as npfrom dataclasses import dataclassfrom typing import List
def calculate_sharpe_ratio(returns: List[float], risk_free_rate: float = 0.04) -> float: """ Sharpe ratio = (Return - Risk-Free Rate) / Standard Deviation
A higher Sharpe means better risk-adjusted returns, NOT higher absolute returns. """ returns_arr = np.array(returns) excess_returns = returns_arr - (risk_free_rate / 252) # Daily risk-free rate
# Annualized Sharpe sharpe = np.sqrt(252) * np.mean(excess_returns) / np.std(excess_returns)
return sharpe
# Compare two scenariosscenario_a = { "name": "AI Trading System", "total_return": 0.22, # +22% "volatility": 0.12, # Lower volatility "sharpe_ratio": 1.50 # Good risk-adjusted return}
scenario_b = { "name": "S&P500 Index", "total_return": 0.24, # +24% "volatility": 0.18, # Higher volatility "sharpe_ratio": 1.11 # Lower risk-adjusted return}
# The AI system has BETTER Sharpe but WORSE absolute return!# This is the trap of optimizing for the wrong metric.My system had a Sharpe ratio of ~1.50 versus the S&P500’s ~1.11. By academic standards, my system looked better. But for an individual trader managing their own capital, total return is what matters.
Total Return Sharpe Ratio ───────────── ─────────────
S&P500: 24% ████████████ S&P500: 1.11 ████████████ AI Sys: 22% ███████████ AI Sys: 1.50 ████████████████
↑ ↑ │ │ BETTER for WORSE for individual traders individual tradersThe problem? I was solving the wrong problem. I had designed my system for institutional metrics when I should have been designing for individual wealth creation.
The Hidden Costs of Active Trading
Every trade costs money. In my multi-agent system, the agents were constantly debating, rebalancing, and adjusting positions. This activity created a significant cost burden:
from dataclasses import dataclass
@dataclassclass TradingCosts: commission_per_trade: float = 0.0005 # 5 bps per trade bid_ask_spread: float = 0.001 # 10 bps spread market_impact: float = 0.0002 # 2 bps market impact slippage: float = 0.0003 # 3 bps slippage
total_per_round_trip: float = 0.004 # ~40 bps per round trip
def calculate_breakeven_alpha( trades_per_day: int, trading_days: int = 378, cost_per_trade: float = 0.004) -> dict: """ Calculate the minimum alpha needed to break even after costs.
For a system trading multiple times per day, the cost burden is massive. """ total_trades = trades_per_day * trading_days total_cost = total_trades * cost_per_trade
# Annualized cost annualized_cost = total_cost * (252 / trading_days)
return { "total_trades": total_trades, "total_cost_pct": total_cost * 100, "annualized_cost_pct": annualized_cost * 100, "breakeven_alpha_needed": annualized_cost }
# Example: If system trades 5 times per dayresult = calculate_breakeven_alpha(trades_per_day=5)# Total cost: ~7.5% of portfolio value# The system needs to generate 7.5% alpha JUST to break even!print(f"Breakeven alpha needed: {result['breakeven_alpha_needed']*100:.2f}%")The calculation revealed another hidden cost layer:
┌────────────────────────────────────────────────────────┐ │ HIDDEN COSTS OF ACTIVE TRADING │ ├────────────────────────────────────────────────────────┤ │ │ │ Transaction Costs (5 trades/day × 378 days) │ │ • Round-trip cost: ~40 bps per trade │ │ • Total: ~7.5% of portfolio │ │ │ │ Computational Costs (25 agents × daily analysis) │ │ • Tokens per analysis: ~50,000 │ │ • Cost per 1k tokens: $0.003 │ │ • Monthly: ~$82.50 │ │ • 18-month total: ~$1,485 │ │ │ │ Time Cost (Development & Maintenance) │ │ • Initial development: ~200 hours │ │ • Ongoing maintenance: ~5 hours/week │ │ • Total: ~500+ hours over 18 months │ │ │ ├────────────────────────────────────────────────────────┤ │ TOTAL COST BURDEN: ~10%+ of returns │ └────────────────────────────────────────────────────────┘The Single Outlier Problem
One comment on the Reddit discussion caught my attention:
“with AVGO called at $152 for +128%”
I went back to analyze my trade attribution:
from dataclasses import dataclassfrom typing import List
@dataclassclass PerformanceAttribution: """ Analyzes whether performance comes from skill or a single lucky bet. """ trades: List[dict]
def analyze_outlier_impact(self) -> dict: """ Check if one trade accounts for most of the returns. """ sorted_trades = sorted(self.trades, key=lambda x: x["return"], reverse=True)
total_pnl = sum(t["pnl"] for t in self.trades) best_trade_pnl = sorted_trades[0]["pnl"] top_3_pnl = sum(t["pnl"] for t in sorted_trades[:3])
return { "total_trades": len(self.trades), "best_trade_pct_of_total": best_trade_pnl / total_pnl, "top_3_trades_pct_of_total": top_3_pnl / total_pnl, "outlier_dependency": best_trade_pnl / total_pnl > 0.3 # Warning if > 30% }
# If AVGO contributed 128% gain but overall portfolio only made 22%,# it means other trades were net negative!# This is a red flag for strategy robustnessThe analysis revealed that one trade (AVGO) contributed a disproportionate amount of the returns. Without that single position, my system would have significantly underperformed even more.
What I Should Have Done Differently
1. Define the Right Objective Function
import numpy as np
# WRONG: Optimize for Sharpe (what I did)def wrong_objective(returns: list, benchmark: list) -> float: """Sharpe ratio can be gamed and doesn't match user goals.""" excess_returns = np.array(returns) - 0.04/252 return np.sqrt(252) * np.mean(excess_returns) / np.std(excess_returns)
# RIGHT: Optimize for user-relevant metrics (what I should do)def right_objective(returns: list, benchmark: list, costs: dict) -> float: """ Multi-objective optimization that considers: 1. Absolute return vs benchmark 2. Risk-adjusted return 3. After-cost performance 4. Drawdown limits """ absolute_return = (1 + np.array(returns)).prod() - 1 benchmark_return = (1 + np.array(benchmark)).prod() - 1 alpha = absolute_return - benchmark_return
# Penalize underperformance heavily if alpha < 0: return alpha * 2 # Double penalty for underperformance
# Reward outperformance with diminishing returns excess_returns = np.array(returns) - 0.04/252 sharpe = np.sqrt(252) * np.mean(excess_returns) / np.std(excess_returns) return np.log(1 + max(0, alpha)) + sharpe * 0.12. Build Complexity Incrementally
The mistake was jumping straight to 25 agents. I should have validated each stage:
Stage 1: Single Agent (3 months) └── Benchmark: Beat S&P500 by 2%+ after costs └── If FAIL: Stop, don't add complexity └── If PASS: Proceed to Stage 2
Stage 2: 3 Agents with Sector Analysis (3 months) └── Benchmark: Improve on Stage 1 by 1%+ └── If FAIL: Revert to Stage 1 └── If PASS: Proceed to Stage 3
Stage 3: 5 Agents with Debate Layer (3 months) └── Benchmark: Reduce max drawdown by 20%+ └── If FAIL: Revert to Stage 2 └── If PASS: Proceed to Stage 4
Stage 4: 25 Agents Full System (6 months) └── Benchmark: Beat S&P500 by 5%+ with lower volatility └── If FAIL: Investigate why complexity doesn't add value3. Track Comprehensive Metrics (Not Just Sharpe)
from dataclasses import dataclass
@dataclassclass PerformanceDashboard: """ Track everything that matters, not just Sharpe. """ # Return metrics total_return: float benchmark_return: float alpha: float # THIS is what matters most
# Risk metrics sharpe_ratio: float sortino_ratio: float max_drawdown: float
# Cost metrics total_transaction_costs: float total_api_costs: float developer_time_hours: float total_cost_pct: float
# Complexity metrics number_of_agents: int daily_trades: int
def is_worthwhile(self) -> dict: """ Answer: Would passive investing have been better? """ passive_return = self.benchmark_return active_effort = self.total_return - self.total_cost_pct benefit = active_effort - passive_return
return { "passive_would_have_returned": passive_return, "active_actually_returned": active_effort, "benefit_of_complexity": benefit, "verdict": "WORTHWHILE" if benefit > 0.02 else "NOT_WORTHWHILE", "hourly_rate": benefit * 100000 / max(self.developer_time_hours, 1) }4. Implement Automatic Kill Switches
import numpy as np
class PerformanceKillSwitch: """ Automatic shutdown when system underperforms. """
def __init__(self, benchmark_return: float, max_underperformance: float = -0.02): self.benchmark = benchmark_return self.max_underperformance = max_underperformance self.rolling_returns = [] self.window = 63 # 3 months of trading days
def check_performance(self, current_return: float) -> dict: """ Check if system should be shut down. """ self.rolling_returns.append(current_return)
if len(self.rolling_returns) < self.window: return {"status": "monitoring", "action": "continue"}
recent_return = (1 + np.array(self.rolling_returns[-self.window:])).prod() - 1 underperformance = recent_return - self.benchmark
if underperformance < self.max_underperformance: return { "status": "kill_switch_triggered", "action": "halt_trading", "reason": f"Underperforming by {abs(underperformance)*100:.1f}%", "recommendation": "Revert to passive index investing" }
return {"status": "operational", "action": "continue"}The Hard Truth
After 18 months and hundreds of hours of development, I had to face reality:
┌────────────────────────────────────────────────────────────┐ │ THE BRUTAL COMPARISON │ ├────────────────────────────────────────────────────────────┤ │ │ │ Option A: Build 25-Agent AI Trading System │ │ • Time invested: 500+ hours │ │ • API costs: ~$1,500 │ │ • Return: +22% │ │ • Stress level: HIGH │ │ │ │ Option B: Buy S&P500 Index Fund │ │ • Time invested: 10 minutes │ │ • Costs: ~$10 (expense ratio) │ │ • Return: +24% │ │ • Stress level: ZERO │ │ │ ├────────────────────────────────────────────────────────────┤ │ CONCLUSION: Option B wins on every metric that matters │ └────────────────────────────────────────────────────────────┘Key Lessons for AI Trading System Builders
Before building a multi-agent trading system, ask yourself:
-
What’s my benchmark? If you can’t beat passive index investing, why build the system?
-
What return do I need to justify the complexity? Add up all costs (transaction, API, time) and set a minimum return threshold.
-
Am I optimizing for the right metric? Sharpe ratio is great for institutions. Total return is what matters for individuals.
-
How will I know if I should shut it down? Define kill switches before you start.
The burden of proof is on the system builder. Complexity must earn its keep.
References
- Atlas-GIC Reddit Discussion
- Sharpe Ratio Definition - Investopedia
- Passive Index Investing - Investopedia
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments