Modular quantitative research and trading lab.
Pipeline: idea → research → backtest → walk-forward → paper → (live)
Nothing enters paper trading without passing a promotion gate. LLMs assist research only. Execution is deterministic.
- Python 3.10+
- uv —
curl -LsSf https://astral.sh/uv/install.sh | sh - Docker + Docker Compose (for Freqtrade)
make
# 1. Clone and enter the repo
git clone <your-private-repo-url> atlas
cd atlas
# 2. First-time setup — installs all workspace packages
make setup
# 3. Download market data (180 days BTC/USDT + ETH/USDT)
make data-download
# 4a. Screening backtest (full-window, no IS/OOS split)
make backtest
# 4b. Walk-forward validation (proper IS/OOS separation)
atlas backtest walk-forward ema_cross --symbol BTC/USDT --timeframe 1h \
--method rolling --is-days 60 --oos-days 30 --test-ratio 0.2
# 5. View results
make results
atlas backtest wf-resultsatlas --help
atlas data download BTC/USDT --days 180
atlas data list
atlas strategies list
# Screening (full-window, no IS/OOS split — quick hypothesis filter only)
atlas backtest run ema_cross --symbol BTC/USDT --timeframe 1h
atlas backtest run ema_cross --no-gate # skip screening gate
atlas backtest results
atlas backtest results --strategy ema_cross --status passed
# Walk-forward validation (IS/OOS separation — required before paper promotion)
atlas backtest walk-forward ema_cross --symbol BTC/USDT --timeframe 1h
atlas backtest walk-forward ema_cross --method anchored --is-days 90 --oos-days 30
atlas backtest walk-forward ema_cross --no-gate # skip WF gate
atlas backtest wf-results
atlas backtest wf-results --strategy ema_cross --status passed
atlas tracker add-hypothesis "EMA cross works in trending markets" \
--rationale "Classic momentum signal" \
--source "Investopedia" \
--tags "momentum,ema"
atlas tracker list-hypotheses
atlas/
├── Makefile
├── pyproject.toml # uv workspace root
├── config.yaml # default config (committed)
├── .env.example # env var template
├── docker-compose.yml # server stack
├── docker-compose.dev.yml # laptop overrides (backtest mode)
│
├── packages/
│ ├── atlas_core/ # config, logging, gates (PromotionGate, WalkForwardGate)
│ ├── atlas_data/ # OHLCV ingestion (CCXT) + SQLite store
│ ├── atlas_strategies/ # strategy interface, registry, examples
│ ├── atlas_backtest/
│ │ ├── runner.py # SimpleVectorizedRunner (screening only)
│ │ ├── splits.py # TimeSeriesSplitter — IS/OOS fold windows
│ │ ├── walk_forward.py # WalkForwardRunner, FoldResult, WalkForwardResult
│ │ └── result.py # BacktestResult dataclass
│ └── atlas_tracker/ # experiment tracker (SQLite → Postgres)
│ # BacktestRun + WalkForwardRun persistence
│
├── cli/
│ └── atlas_cli/ # Typer CLI entrypoint
│
├── services/
│ └── freqtrade/
│ ├── Dockerfile
│ ├── config/ # Freqtrade configs (dry-run, backtest)
│ └── strategies/v1/ # Freqtrade IStrategy implementations
│
├── data/ # gitignored at runtime; .gitkeep only
└── tests/unit/
├── test_splits.py # TimeSeriesSplitter unit tests
└── test_walk_forward.py # WalkForwardRunner + WalkForwardGate tests
Config is loaded in priority order (highest wins):
ATLAS_*environment variables.envfileconfig.yaml- Field defaults
cp .env.example .env
# Edit .env as neededKey config fields in config.yaml:
| Field | Default | Description |
|---|---|---|
exchange |
binance |
CCXT exchange ID |
default_pairs |
[BTC/USDT, ETH/USDT] |
Pairs to download |
gate_min_sharpe |
0.5 |
Minimum OOS Sharpe for Gate 1 |
gate_max_drawdown |
0.25 |
Maximum drawdown for Gate 1 |
gate_min_trades |
30 |
Minimum trade count for Gate 1 |
gate_max_params |
10 |
Maximum free parameters |
# Paper trading (dry-run, server)
make docker-up
# → FreqUI at http://localhost:8080 (user: atlas, pass: see config.json)
# Freqtrade backtest via Docker (laptop)
make docker-dev
# Logs
make docker-logsBefore running Docker: change the jwt_secret_key and password fields
in services/freqtrade/config/config.json.
Applied to full-window backtest results. A coarse filter — passing this gate does NOT mean a strategy is ready for paper trading.
| Check | Threshold |
|---|---|
| Full-window Sharpe ratio | ≥ 0.5 |
| Max drawdown | ≤ 25% |
| Trade count | ≥ 30 |
| Free parameters | ≤ 10 |
Applied to OOS (out-of-sample) metrics only. All checks must pass. The
is_oos_degradation check catches catastrophic overfitting.
| Check | Threshold |
|---|---|
| Mean OOS Sharpe | ≥ 0.4 |
| Mean OOS drawdown | ≤ 30% |
| Fold count | ≥ 3 |
| Mean OOS trades/fold | ≥ 10 |
| IS/OOS Sharpe ratio | ≥ 0.4 |
Thresholds are hardcoded in gates.py. Lower them only with justification.
make test # unit tests
make test-cov # with coverage report- Create
packages/atlas_strategies/atlas_strategies/your_strategy.py:
from atlas_strategies.base import BaseStrategy
from atlas_strategies.registry import registry
@registry.register
class MyStrategy(BaseStrategy):
NAME = "my_strategy"
VERSION = "1.0.0"
DESCRIPTION = "..."
def default_params(self):
return {"period": 14}
def generate_signals(self, df):
df = df.copy()
# ... add indicators and signal/crossover columns
return df- Import it in
atlas_strategies/examples/__init__.py(or a new module). - Test:
atlas backtest run my_strategy
For Freqtrade integration, also create the IStrategy version in
services/freqtrade/strategies/v1/.
| Phase | Focus | Status |
|---|---|---|
| 0 | Foundation: repo, data, vectorized screening backtest, experiment tracker | Done |
| 1 | Walk-forward validation: IS/OOS splits, WalkForwardRunner, WalkForwardGate | Current |
| 2 | Freqtrade backtest wrapper (rigorous OOS), full walk-forward harness | Next |
| 3 | Paper trading 24/7, monitoring dashboard | Planned |
| 4 | Live trading (strict gate required) | Future |
Honest current status (Phase 1): Walk-forward uses SimpleVectorizedRunner
internally — long-only, flat fee, no slippage. The IS/OOS split is real and
the hold-out logic is correct. The OOS metrics are meaningful as a relative
signal (strategy A vs B) but not realistic enough for absolute promotion
decisions. FreqtradeRunner (Phase 2) will replace the inner runner for that.