Skip to content
jicheolhaPublic

About

Automatic crypto futures trading bot using Bollinger Band Squeeze strategy. Includes backtesting, optimization, and live trading modules.

Topics

Resources

Stars

1 star

Watchers

2 watching

Forks

Repository files navigation

Keltrader

Full-stack quantitative crypto trading system: Bollinger-Band-Squeeze strategy on 2h price bars with 1-minute intra-bar exit precision, trading Coinbase Advanced Trade perpetual futures (XRP, ETH, SOL) with real margin math. Multi-sampler Optuna optimization, a six-test statistical validation pipeline, live dashboard hosted on a DigitalOcean VPS, and Telegram alerting.

Current state (honest)

  • Live-enabled assets: XRP, ETH, SOL (BTC and DOGE params exist in params.py but are not enabled for live execution).
  • The live bot has executed zero fills since 2026-06-06. The futures account balance is $40.98 and each contract requires $290+ margin, so the sizing routine rejects every signal with size=0.0. The strategy math still runs, but no trades occur without a deposit.
  • BTC live params fail the deep validator's out-of-sample gate (holdout PF 0.80, negative PnL). They are in the file for historical reference and pending replacement — not for deployment.
  • BTC re-optimization is running now. 5 concurrent Optuna processes (BTC random, BTC TPE, BTC CMA-ES, DOGE random, DOGE TPE) plus a continuous "deep validator" daemon that scans the DBs for high-scoring candidates and runs the same statistical gate the live params must pass. Of 157 BTC candidates with Optuna score ≥ 0.80 tested to date, zero have passed the baseline validation gate.

Measured performance (holdout 2025-01-01 → 2026-07-11, live params on historical data)

Backtest reruns the currently-deployed params.py on 18 months of unseen data using the same signal generator + 1-min exit precision + real perpetual-futures margin math the live bot uses.

Live-parity backtest (matches how the bot would execute)

Asset Trades Win rate PnL
XRP 28 67.9% +$2,425
ETH 38 57.9% +$2,057
SOL 64 51.6% +$1,842
Portfolio 130 +$6,324

Real margin rates (XRP 37.3%/56.6%, ETH 24.6%/33.5%, SOL 36.6%/56.0% for long/short), 10 bps slippage per side, 10 bps commission per side, 60-second signal poll delay, integer contract sizing.

Full metrics (idealized backtest, per-asset)

Metric XRP ETH SOL
Total PnL +$2,904 +$4,184 +$2,694
Annualised return +18.6% +26.4% +17.3%
Buy-and-hold same window −55.1% −52.7% −61.9%
Alpha vs. underlying (ann) +17.4% +24.5% +17.2%
Beta to underlying ~0.00 ~0.01 ~0.00
Profit factor 2.78 1.93 1.42
Annualised Sharpe 2.09 1.54 1.05
Annualised Sortino 7.03 3.96 2.79
Max drawdown 5.8% 11.2% 14.6%
Max time underwater 87 days 202 days 306 days
Calmar ratio 3.95 2.36 1.32
Ulcer Index 2.25 4.43 5.90
Monte-Carlo p-value 0.008 0.041 0.108
Bootstrap PF 95% CI [1.28, 8.36] [0.98, 4.20] [0.86, 2.54]
Deflated Sharpe (selection-bias corrected) 10.9% 2.4% 0.4%

Portfolio-level

  • Joint Sharpe: 2.45 (higher than any individual asset — inter-asset correlations near zero)
  • Joint max drawdown: 10.6%
  • Correlation of daily PnLs: XRP↔ETH = −0.02, XRP↔SOL = +0.09, ETH↔SOL = +0.22

What's honest that most trading-bot READMEs skip

  • Deflated Sharpe is sobering. After correcting for the ~14,000 parameter trials tested per asset, the probability that the observed edge is real (rather than a lucky pick from many attempts) drops to single-digit percentages for ETH and SOL. The economic case — positive alpha while underlying assets were down 50–60% — is stronger than the statistical case for any single asset. The portfolio-level Sharpe is what carries the credibility.
  • The live bot has been effectively paused for weeks. Account funding is the bottleneck, not signal quality. Any "live P&L" figure on the dashboard reflects a stale state file, not current trading activity.
  • BTC search has stalled. After ~4 days of continuous optimization across three samplers, the objective best has crept up by 0.04 for TPE and 0.02 for random. Zero of the resulting high-score candidates have cleared even the baseline validation gate that ETH and SOL live params both pass.

What makes this different from a typical crypto bot

1. Statistical validation pipeline goes deeper than "did the backtest look good?"

Every candidate parameter set must pass six independent tests before being marked as tradeable:

Test What it detects
Baseline train + holdout Basic profitability and out-of-sample survival
Monte Carlo bootstrap on trade sequence Whether the equity path is distinguishable from random ordering
Random-Entry MC (novel) Whether the signal has entry edge, or if the ATR-based exit geometry is doing all the work
Parameter Perturbation MC (novel) Whether the parameter set is on a robust hill or a lucky spike (isolated peak → reject)
Deflated Sharpe Ratio (Bailey/López de Prado) Corrects observed Sharpe for the ~14,000 trials tested per asset
Bootstrap 95% CIs on PF, Sharpe, expectancy Replaces point estimates with distributions

The Random-Entry MC in particular kills many superficially-good candidates: if a monkey with the same trade frequency and same ATR exit rules would have made the same PnL, the signal has no edge. For XRP/ETH/SOL live params, the observed PF sits at the 96th–100th percentile of random-entry runs. For a top-scoring BTC candidate (#3445, Optuna score 0.8252), it sat at the 78th percentile — real strategies clear ≥95th, so it was rejected.

2. Live-parity backtest with actual margin math

The "expected PnL" curve on the dashboard is not a simplified backtest. It uses:

  • The exact contract sizes and margin rates each product would demand on Coinbase (XRP-PERP-INTX, ETP-20DEC30-CDE, etc.)
  • Integer-contract sizing that rejects trades if the margin budget can't afford one full contract
  • 60-second poll delay matching the live bot's signal check frequency
  • 10-bps slippage and 10-bps commission per side
  • 1-minute intra-bar TP/SL detection (not 2h bar-close approximation)

The result is what the bot would have realized if it had been running and funded during the holdout.

3. Multi-sampler Optuna optimization with SQLite persistence

Three samplers run in parallel per asset:

  • Random — 30,000 trials, broad exploration
  • TPE (tree-structured Parzen estimator) — 10,000 trials, warm-started from random
  • CMA-ES — 100 seeds × 200 trials each, evolutionary refinement around promising regions

All state persists in per-asset SQLite databases (~600 MB total for BTC alone), so runs survive laptop sleep, kernel updates, and cross-day resumption. Combined trial count as of writing: ~29,000 valid BTC trials, ~22,000 DOGE, ~15,000 each for XRP/ETH/SOL.

4. Continuous validation daemon

A background process (dashboard/btc_deep_validator.py) scans the BTC optimization DBs every 30 minutes, picks up unseen trials with Optuna score ≥ 0.80, and runs the full six-test pipeline on each. Results are logged to a JSONL file for later review, and a Telegram alert fires only for candidates that pass every test — a much stricter gate than the previous "high Optuna score" alert.

5. Live dashboard (FastAPI + Chart.js + Tailwind)

Hosted on the same VPS as the live bot, gated by nginx basic auth. Shows:

  • Real-time account balance, positions, buying power
  • Live equity curve overlaid with the expected (backtest) curve
  • Daily PnL bars
  • Per-asset state (last signal, indicator values, cooldowns)
  • Recent trades table
  • Event feed (state saves, entries/exits, warnings)
  • Bot health (uptime, error count, last heartbeat)

Data pipeline: daily cron regenerates the expected-PnL curve using incremental tail-append cache updates (avoids Coinbase rate-limit truncation that silently destroyed the cache during an earlier deployment).

6. Telegram alerting

Notable candidate alerts, live entry/exit notifications, error alerts, daily heartbeats. Uses environment-scoped secrets, no plaintext tokens in the repo.

Architecture

keltrader/
├── params.py                       # Single source of truth for per-asset live params
├── run_backtest.py                 # Backtest runner — spot and leveraged modes
├── run_live_multi_asset.py         # Live trading entry point
├── optimizer.py                    # Optuna optimizer (random / TPE / CMA-ES)
├── validate_params.py              # Post-optimization validation (holdout + MC + permutation)
│
├── strategy/
│   ├── technical.py                # BB, KC, squeeze state, momentum, RSI, volume, ATR
│   └── signal_generator.py         # Indicators → entry direction + ATR SL/TP + sizing
│
├── core/
│   ├── backtester.py               # Event-driven backtester (spot + leveraged futures)
│   ├── monte_carlo.py              # Bootstrap MC on trade sequence
│   ├── permutation_test.py         # Price-shuffle permutation test
│   ├── ensemble.py                 # Multi-param ensemble signal aggregation
│   └── experiment_tracker.py       # SQLite log of backtest/optimization runs
│
├── live/
│   └── coinbase_live_trader.py     # Live engine: signal poll loop, order placement,
│                                   # OCO bracket management, state persistence, Telegram
│
├── dashboard/
│   ├── server.py                   # FastAPI backend
│   ├── static/{index.html,app.js}  # Tailwind + Chart.js frontend
│   ├── regen_backtest.py           # Daily live-parity expected-PnL regeneration
│   ├── refresh_caches.py           # Incremental tail-append cache updates
│   ├── cron_regen_and_push.sh      # Daily cron: refresh caches → regen → scp to VPS
│   ├── btc_deep_validator.py       # Continuous BTC candidate validation daemon
│   └── opt_telegram_watcher.py     # Optuna-score-based Telegram alerts
│
├── data/
│   └── data_utils.py               # Multi-timeframe Coinbase data with SQLite/pickle cache
│
├── db/                             # Per-asset Optuna study databases (SQLite)
└── tests/                          # Pytest suite

Strategy details

Entry logic (Bollinger-Band Squeeze):

  1. Bollinger Bands contract inside Keltner Channels (compression = squeeze)
  2. Squeeze must persist for at least N bars (asset-specific; XRP=5, ETH=3, SOL=3)
  3. Squeeze release + momentum acceleration triggers entry
  4. Volume-ratio filter confirms breakout direction
  5. RSI filter rejects entries at asset-specific overbought/oversold extremes
  6. ATR-scaled stop loss and take profit are set at entry (asymmetric R:R)
  7. Trailing-stop activation once price moves N × risk in favor
  8. Position size scales with signal quality (squeeze duration × volume × momentum)

Exit logic:

  • Intra-bar TP/SL detection using 1-minute bars
  • OCO bracket orders in live execution (both TP and SL sit as limit/stop orders on the exchange)
  • Time-based max hold (asset-specific)

Per-asset parameters are independently optimized against the same objective function and stored in params.py. The live bot reads them at startup; the backtester reads the same file.

Backtesting

python run_backtest.py                                          # All default assets
python run_backtest.py --symbols ETH/USD                        # Single asset
python run_backtest.py --symbols ETH/USD,XRP/USD,SOL/USD        # Multiple
python run_backtest.py --leverage --maintenance-margin 0.666    # Futures mode

Optimization

python optimizer.py --symbol BTC --signal-tf 2h --method random --random-trials 30000
python optimizer.py --symbol BTC --signal-tf 2h --method tpe --tpe-trials 10000
python optimizer.py --symbol BTC --signal-tf 2h --method cmaes --cmaes-trials 200
python optimizer.py --symbol BTC --show-best                    # Report top candidates
python optimizer.py --symbol BTC --evaluate-holdout             # Post-hoc OOS check

Objective function: cross-validated composite score with hard gates (PnL > 0, PF ≥ 1.2, MDD ≤ 25%, Sharpe > 0, min-trades scaled by fold length). Trials persist to SQLite; runs are safely resumable.

Live trading

export COINBASE_API_KEY="your_key"
export COINBASE_API_SECRET="your_secret_pem"
export TELEGRAM_BOT_TOKEN="..."
export TELEGRAM_CHAT_ID="..."

python run_live_multi_asset.py --symbols XRP,ETH,SOL
python run_live_multi_asset.py --paper                          # No real orders
python run_live_multi_asset.py --list-futures                   # Show available contracts

The live engine loads params.py, connects to Coinbase Advanced Trade, and enters a signal poll loop. On each 2h close it checks for BB Squeeze release + volume + RSI + trend filter; if all pass, sizes the position against real futures margin, places an OCO bracket (entry limit + TP + SL), persists state, and posts to Telegram. State is written to disk every hour so restarts pick up correctly.

Dashboard

FastAPI backend under dashboard/server.py, static frontend under dashboard/static/. Deployed on the same VPS as the live bot behind nginx with basic auth. Data files (equity curves, trades, health) live under dashboard/static/data/; the daily cron regenerates them.

Data pipeline

dashboard/refresh_caches.py handles incremental tail-append: loads the existing pickle cache, fetches only the bars from the last cached timestamp to now, appends and saves. This avoids the Coinbase rate-limit truncation problem where a full-window refetch returns only a partial batch and destroys the cache. The old naive cron used a full refetch and destroyed 16 months of 1-min history in three days before the bug was identified.

Tests

pytest tests/

Covers scoring, signal generation, technical indicators, Monte Carlo, ensemble, optimizer internals.

Disclaimer

For informational purposes only. Past backtest performance does not guarantee future results. Deflated Sharpe values in single-digit percentages mean the observed edge cannot be statistically distinguished from lucky selection out of thousands of trials at the individual-asset level. Portfolio-level Sharpe (uncorrelated-asset averaging) is the more defensible number.

License

MIT

About

Automatic crypto futures trading bot using Bollinger Band Squeeze strategy. Includes backtesting, optimization, and live trading modules.

Topics

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages