Auto-ResearchTrading

An AI agent that taught itself to trade crypto
103 experiments, zero human intervention

github.com/Nunchi-trade/auto-researchtrading

392 stars · Python · Hyperliquid perpetual futures

ELI5

The simple version anyone can understand

What if a robot tried 100 trading strategies while you slept?

Think of it like a cooking competition

  • You have one recipe card (the strategy file)
  • A robot chef tweaks the recipe, tastes the result, and only keeps changes that taste better
  • After 103 rounds of tweaking, the robot chef created a recipe that scores 7.9x better than the original

The punchline: The best changes were removing ingredients, not adding them. Simpler was better.

The Experiment Loop

Claude Code (an AI coding agent) runs this loop over and over, fully autonomously:

Read strategy.py
→
AI tweaks the code
→
Backtest on
9 months of data
→
Score improved?

Yes — keep it!

Commit the change, record the new score, and try to improve further.

No — throw it away

Revert the change, go back to the last good version, try something else.

Constraint: The AI can only edit one file. Everything else is locked.

From Simple to Spectacular

2.7
Starting Sharpe
21.4
Final Sharpe
103
Experiments
0.3%
Max Drawdown

What's a Sharpe ratio? It measures return vs. risk. A Sharpe of 2 is "very good" in traditional finance. 21.4 is extraordinary — though this is on historical data, not live trading.

The robot discovered that removing fancy features (pyramid trading, BTC filters, correlation weighting) made the strategy dramatically better.

The Winning Strategy in Plain English

The final strategy works like a panel of 6 judges voting on every trade:

1. Momentum

"Is the price going up over the last 12 hours?"

2. Velocity

"Is the price going up over the last 6 hours?"

3. EMA Cross

"Is the short-term average above the long-term?"

4. RSI

"Is there buying strength right now?"

5. MACD

"Is momentum accelerating?"

6. Bollinger Bands

"Is volatility compressed? (a breakout may be coming)"

The rule: At least 4 out of 6 judges must agree before any trade happens. This filters out noise.

Technical Deep Dive

Architecture, code patterns, scoring, and what the AI actually learned

Repository Architecture

Core Files

strategy.pyThe only editable file
backtest.pyFixed evaluation harness
prepare.pyData download + scoring engine
run_benchmarks.py5 reference strategies
program.mdAI agent instructions

Key Design Constraint

Only strategy.py is modifiable. The agent cannot change the backtester, the data pipeline, or the scoring function.

This is the "Karpathy-style" research pattern: constrain the search space, automate the iteration.

Dependencies (locked)

numpy pandas scipy requests pyarrow

Data Pipeline & Backtesting Engine

Data Specification

  • Symbols: BTC, ETH, SOL (Hyperliquid perps)
  • Interval: 1-hour OHLCV bars
  • Validation: 2024-07-01 to 2025-03-31 (9 months)
  • Source: CryptoCompare + Hyperliquid funding rates
  • Lookback buffer: 500 bars per symbol

Data Splits

SplitPeriod
Train2023-06 to 2024-06
Validation2024-07 to 2025-03
Test2025-04 to 2025-12

Backtest Parameters (Fixed)

Initial capital$100,000
Maker fee2 bps
Taker fee5 bps
Slippage1 bps
Max leverage20x
Time budget120 seconds

Funding rates applied every bar (1/8th scaling since funding settles every 8h). Longs pay when positive, shorts receive.

The Scoring Formula

The AI optimizes a single composite score — not just raw returns:

score = sharpe * sqrt(min(trades / 50, 1.0))
        - drawdown_penalty
        - turnover_penalty

Component Breakdown

  • Sharpe ratio — annualized from hourly returns (x sqrt(8760))
  • Trade count factor — rewards having at least 50 trades (penalizes lazy strategies)
  • Drawdown penalty — 0 under 15%, then 5x per additional %
  • Turnover penalty — penalizes churning over 500x annual

Hard Cutoffs

  • Fewer than 10 trades → -999
  • Max drawdown > 50% → -999
  • Final equity < 50% of initial → -999

These prevent degenerate strategies from polluting the search.

Strategy Interface & Contract

Every strategy must implement a Strategy class with a single method:

class Strategy:
    def __init__(self):
        ...  # initialize state

    def on_bar(self, bar_data, portfolio) -> list[Signal]:
        ...  # return target positions

BarData (per symbol)

.closeCurrent close price
.historyDataFrame of last 500 bars
.funding_rateCurrent funding rate

Signal Output

Signal(
  symbol="ETH",
  target_position=8000.0  # USD notional
)   # +long, -short, 0=flat

Signals are target positions, not deltas. The backtester handles execution, fees, and slippage.

The Six-Signal Ensemble

The winning strategy uses a majority-vote ensemble requiring 4/6 agreement:

1. Momentum (12h)

ret_short > dyn_threshold

12-bar return vs volatility-adjusted threshold (0.5% – 2%)

2. Velocity (6h)

ret_vshort > thresh * 0.7

Shorter lookback at 70% threshold for responsiveness

3. EMA Cross (7/26)

ema_fast > ema_slow

Fast EMA (7) crossing above slow EMA (26)

4. RSI(8)

rsi > 50 (bull) / < 50 (bear)

Key finding: period 8 >>> 14 for hourly crypto

5. MACD (14/23/9)

macd_histogram > 0

Non-standard params tuned for hourly timeframe

6. BB Compression

bb_pctile < 90

Bollinger width below 90th pctile = pending breakout

Volatility-Adaptive Thresholds

Entry signals use a dynamic threshold that adapts to current market volatility:

realized_vol = std(log_returns[-36:])
vol_ratio    = realized_vol / 0.015   # target vol
dyn_threshold = 0.012 * (0.3 + vol_ratio * 0.7)
dyn_threshold = clamp(0.005, 0.020)    # floor/ceiling

Why This Matters

  • Low volatility: threshold drops to 0.5%, catching subtle moves
  • High volatility: threshold rises to 2.0%, filtering noise
  • Prevents whipsaws during choppy markets
  • Allows entry during quiet breakouts

Key Parameters

VOL_LOOKBACK36 bars (1.5 days)
TARGET_VOL0.015 (1.5%/hr)
BASE_THRESHOLD0.012 (1.2%)
Range0.5% – 2.0%

Exit Mechanics

Three independent exit triggers, any one closes the position:

1. ATR Trailing Stop (5.5x)

Tracks peak price since entry. Exits when price drops 5.5 ATR units from peak. The wide multiplier gives trades room to breathe.

peak = max(peak, current_price)
stop = peak - 5.5 * atr
if price < stop: exit()

2. RSI Mean Reversion

Long exits at RSI > 69 (overbought). Short exits at RSI < 31 (oversold). These catch exhaustion points.

3. Signal Reversal

If the ensemble flips (4/6 bearish while long, or 4/6 bullish while short), the position reverses immediately. No waiting.

Cooldown Period

2-bar cooldown after every exit prevents rapid-fire re-entry. Reduces churning and noise trading.

Position Sizing

8% of equity per trade, split equally across BTC/ETH/SOL (33% weight each).

The Autonomous Research Loop

Defined in program.md — instructions the AI agent follows:

Read current
strategy.py
→
Read
results.tsv
→
Propose
hypothesis
→
Edit strategy
→
Git commit
Run
uv run backtest.py
→
Score > best?
→
Keep & record
 or 
Revert & retry

Key insight: The agent reads the full history of past experiments before each attempt. It learns from its own failures — like having an ever-growing lab notebook. The 120-second time budget per backtest prevents runaway compute.

Strategy Evolution: 7 Phases

103 experiments across 7 distinct phases of discovery:

Phase 1: Baseline to Ensemble (2.7 → 8.4)

Added ensemble voting. Requiring multiple signals to agree was the single biggest breakthrough.

Phase 2: ATR Stops (8.4 → 9.4)

Widened ATR trailing stops from 3.0x to 5.5x. Gave winning trades more room.

Phase 3: Bollinger Bands (9.4 → 10.3)

Added BB width compression as a 6th signal — detects pending breakouts.

Phase 4: The Great Simplification (10.3 → 14.8)

Removed pyramiding, funding boosts, BTC filters, correlation adjustments, and strength scaling. +4.5 points from subtraction alone.

Phase 5-7: Parameter Tuning (14.8 → 21.4)

RSI period 8 (was 14). This single change added 5 Sharpe points. MACD tuned to 14/23/9.

The Great Simplification

Removing complexity improved performance.

Each "sophisticated" feature was tested — and hurt the score:

Feature RemovedImpact on ScoreWhy It Hurt
Strength-scaled sizing +1.7 Overfit to magnitude of signals
BTC lead-lag filter +0.8 Added lag, missed entries
Pyramiding (adding to winners) +0.6 Increased drawdown on reversals
Funding rate carry boost +0.5 Noise in short-term funding
Correlation weighting +0.4 Regime-dependent, unreliable
Drawdown risk reduction +0.5 Drawdown so low it never triggered

Total gain from simplification: +4.5 Sharpe points. The "dead code" approach: set thresholds to unreachable values (99.0, -99.0) to neutralize features.

The RSI Period Discovery

The single most impactful parameter change in the entire research:

RSI(14)
Standard

Score: 14.8

RSI(8)
Discovered

Score: 19.7

+5.0 Sharpe points from changing one number.

Why RSI(8) Works for Hourly Crypto

  • RSI(14) was designed for daily bars — 14 days = 2 weeks
  • RSI(8) on hourly bars = 8 hours = 1/3 of a day
  • Crypto markets move faster than equities
  • Shorter RSI captures intraday momentum shifts
  • Standard 14 is too slow — signals arrive after the move

This is a general lesson: technical indicator defaults are calibrated for daily equities. Always re-tune for your timeframe and asset class.

Code Walkthrough: on_bar()

The core execution path each bar (simplified):

def on_bar(self, bar_data, portfolio):
    signals = []
    for symbol in ["BTC", "ETH", "SOL"]:
        closes = bar_data[symbol].history["close"].values

        # 1. Compute dynamic threshold from realized vol
        dyn_threshold = calc_vol_threshold(closes)

        # 2. Calculate 6 signals
        mom_bull    = ret_12h(closes) > dyn_threshold
        vshort_bull = ret_6h(closes)  > dyn_threshold * 0.7
        ema_bull    = ema(7) > ema(26)
        rsi_bull    = rsi(8) > 50
        macd_bull   = macd_hist(14,23,9) > 0
        bb_bull     = bb_width_pctile(7) < 90

        # 3. Majority vote (4 of 6)
        bull_votes = sum([mom, vshort, ema, rsi, macd, bb])
        if bull_votes >= 4: go_long()

        # 4. Exit: ATR trail | RSI extreme | signal reversal
        if price < peak - 5.5*atr: exit()
        if rsi > 69 and is_long(): exit()
    return signals

Full implementation: ~250 lines of Python. No external models, no ML — pure signal processing.

Benchmark Strategies

5 reference strategies are provided in benchmarks/ for comparison:

StrategyTypeDescription
momentum_breakout Trend The original baseline (Sharpe 2.7)
mean_reversion Mean Revert Fade extreme moves, bet on return to mean
avellaneda_mm Market Making Avellaneda-Stoikov optimal market making
regime_mm Adaptive MM Regime-switching market maker
funding_arb Carry Exploit funding rate premiums

The leaderboard runner (run_benchmarks.py) loads each strategy, backtests on val data, and ranks by composite score. The autonomous agent's final strategy outperforms all five by 7.9x+.

Final Results

21.4
Sharpe Ratio
0.3%
Max Drawdown
7,949
Total Trades
7.9x
vs. Baseline

Performance Progression

MilestoneScoreKey Change
Baseline2.7Simple momentum
Ensemble8.44/6 majority vote
+ BB width10.36th signal added
Simplified14.8Removed 6 features
Final21.4RSI(8) + param tuning

Reality Check

  • This is in-sample validation data, not live trading
  • Sharpe 21 is unrealistic for production — expect significant degradation
  • No test-set results shown (out-of-sample holdout)
  • 3 assets, 9 months — narrow universe

The value is in the process, not the final Sharpe number.

Pattern: Dead Code via Extreme Thresholds

The strategy file contains "dead" features neutralized by setting params to unreachable values:

# These features exist in code but are effectively disabled:

FUNDING_BOOST     = 0.0       # multiplier = 1.0, no effect
TAKE_PROFIT_PCT   = 99.0      # never triggers at 99x
BTC_OPPOSE_THRESHOLD = -99.0 # BTC filter effectively off
PYRAMID_SIZE      = 0.0       # adds 0 to position
HIGH_CORR_THRESHOLD = 99.0   # correlation never this high
DD_REDUCE_THRESHOLD = 99.0   # drawdown never reaches 99%

Why not delete the code? The AI agent keeps the infrastructure so it can re-enable features in future experiments. Setting extreme thresholds is a reversible way to test the effect of removing a feature without losing the implementation.

This is a fascinating artifact of autonomous code evolution — code that documents its own failed hypotheses.

Broader Lessons

For AI-Driven Research

  • Constrain the search space. One editable file + fixed harness = focused iteration
  • Automate the feedback loop. Score, git commit, record — no human bottleneck
  • Maintain a lab notebook. The AI reads results.tsv to learn from past failures
  • Budget computation. 120s time limit prevents expensive experiments

For Quantitative Trading

  • Defaults are wrong. Standard indicator periods (RSI 14, etc.) assume daily equities
  • Simplicity wins. 6 features removed = +4.5 Sharpe. Less overfitting, better generalization
  • Ensemble > single signal. Majority vote filters noise better than any one indicator
  • Dynamic thresholds. Vol-adjusted entry avoids regime mismatch

The meta-lesson: An AI agent with 103 tries and a good scoring function can discover what a human quant might take weeks to find — and the biggest discovery was removing complexity, not adding it.

Resources & Links

Repositorygithub.com/Nunchi-trade/auto-researchtrading
Stars392 (and growing)
LanguagePython 3.10+ (numpy, pandas, scipy)
Quick startuv run prepare.py && uv run backtest.py
Full logSTRATEGIES.md — all 103 experiments
AI promptprogram.md — the autonomous loop instructions

agent37.club