An open-source crypto paper-trading desk with a backtest farm, a walk-forward optimizer, and sub-minute candles built from the raw trade tape. Bring your own strategy. Bring your own exchange.
Laravel 13 · Vue 3 · Redis · MySQL/MariaDB · TradingView lightweight-charts.
NFA. Paper first. Start with a number you are fine watching hit zero.
Fifteen minutes, every feature, explained like you're five. Install paths, the first-run wizard, the charts, the desk cycle, backtests, the optimizer, and a deep dive on building a strategy.
Chapters
| 0:13 | Why I built this, and the honest warning |
| 1:29 | Three ways to run it |
| 2:49 | First-run setup wizard |
| 3:16 | Why a paper desk still needs an exchange |
| 5:05 | Charts and sub-minute candles |
| 6:05 | The desk: scan, vet, size, fills |
| 7:14 | Backtests and why fees matter |
| 7:52 | Optimizer and walk-forward |
| 9:21 | Strategy builder |
| 11:53 | Take profit, the ladder and the runner |
| 12:44 | Pi re-entry |
| 13:47 | The stop that re-anchors |
| 14:40 | The built-in AI assistant |
Prefer to watch offline? The 1080p mp4 and captions.srt are attached to the
v0.2.1 release.
SCAN → VET → SIZE → FILLS one cycle, every scan.poll_seconds
RISK own timer, final authority
CHIEF health, heartbeats, halt switch, reports
- 110 exchanges behind one contract. Set
EXCHANGE=krakenand go. - Paper desk that simulates market orders against the live bid/ask with configurable slippage. No exchange key needed.
- Candles you can't get from an exchange: 13s, 15s, 20s, 25s, 30s, 33s, 41s, 45s, 49s built from the trade tape, plus 90s, 2m, 3m, 4m, 10m derived by resampling, plus every standard timeframe.
- Backtester that replays a strategy over any window, and a farm that fans a parameter grid out to every worker box you own.
- Optimizer that runs random or hill-climb candidates per coin, trains and tests walk-forward, and only promotes out-of-sample winners.
- Dashboard with live chart, positions, runs, backtests, and every tunable editable in place.
- Live mode behind a double confirm (
DESK_MODE=liveandDESK_LIVE_CONFIRM=yes). Nothing is sent without both.
110 exchanges out of the box. Coinbase (spot + US perpetual futures) has a native adapter, and a generic ccxt adapter covers everything ccxt speaks for spot market data and orders: Binance, Kraken, Bybit, OKX, KuCoin, Bitget, Gate, HTX, MEXC, Bitfinex, Gemini, Bitstamp and the rest. Full roster in docs/EXCHANGES.md.
Zero PHP, zero Node. Docker gives you the whole desk — dashboard, trading loop, queue, scheduler, Reverb websockets, Coinbase feeder, MariaDB, Redis — behind HTTPS on one port.
git clone https://github.com/shoemoney/shoemoneyx.git && cd shoemoneyx
docker/up.sh # or: bin/desk compose
open https://localhostFirst run generates .env and prints a one-time MASTER_PASSWORD — log in with it once and you'll be asked to choose your own password (12+ characters, typed twice); after that the printed key opens nothing, and your browser stays signed in for about a year. Scripts send your password as the X-Desk-Token header. Switching to live mode asks for the password again. On a manual install with no MASTER_PASSWORD in .env, the first visitor sets the password, so set one before exposing the box (keyless setup is refused from public IP addresses). Upgrading from an older install that chose "no password" drops that empty choice, so the desk asks for a password on the next visit from a local or private-network address; the migration logs a warning when it does. Rerunning docker/up.sh is a no-op except starting containers.
Update to the latest image:
docker compose pull && docker compose up -dcp .env.example .env # set DB_*, keep DESK_MODE=paper
bin/desk setup # composer, npm, key, migrate, product sync, candle backfill
bin/desk dev # vite + artisan serve + queue worker + desk:run
open http://localhost:8000First run lands you on /onboarding — choose your own master password (required), connect an OpenRouter key, pick an exchange, optionally import a community strategy, and launch in paper mode, all in under five minutes.
Charts run on TradingView's lightweight-charts (Apache-2.0), bundled via npm — no separate download or TradingView account needed.
| Command | What |
|---|---|
php artisan desk:run |
the CHIEF loop: products hourly, candles every minute, RISK every 60s, cycle every 5 min |
php artisan desk:cycle |
one SCAN→VET→SIZE→FILLS pass |
php artisan desk:risk |
one RISK sweep |
php artisan desk:ctl status|halt|resume|stop|close <PRODUCT|all>|paper-reset|set <key> <v>|get <key> |
controls |
php artisan desk:backtest --products=BTC-USD,SOL-USD --days=30 --cash=1000 |
replay a strategy |
php artisan desk:sweep --queue |
fan a parameter grid out to the farm |
php artisan desk:optimize --space=mr|mr-short --mutate=N |
walk-forward optimizer |
php artisan desk:coin show|set|clear |
per-coin parameter overrides |
php artisan market:sync-products / market:backfill --days=90 / market:backfill-trades --timeframes=33s,41s |
data |
php artisan coinbase:account --key=… --secret=… / --test |
store (encrypted) and test an exchange key |
bin/desk wraps the common ones.
The contract is app/Desk/Contracts/Strategy.php. Every decision sees one ProductStats row: volumes, price changes, buy/sell tape, spread, book depth, indicators.
Two strategies ship as examples. MeanReversionStrategy is the default. CustomStrategy is the template: override any of scan(), vet(), size(), risk(), add tunables in defaults() and they appear on the Settings page. Select with DESK_STRATEGY=custom or the dropdown.
Parameters layer as config/desk.php ← strategy defaults() ← the settings table ← per_product.<PAIR>.<key>.
This repository ships example strategies only. Your edge is yours.
- Ten generic starter templates (mean reversion, breakout, EMA cross, Bollinger, VWAP, MACD, volume spike, trend-follow, DCA grid, and a re-entry teaching example) are cataloged in
docs/STARTER_TEMPLATES.md.
Any machine that can reach the desk host's Redis can be a worker: bin/desk worker N, or the Docker image with ROLE=worker. Roster and expected worker counts come from FARM_HOSTS and FARM_EXPECTED_WORKERS in .env. Details in docs/FARM.md.
RISK is final authority, on its own timer, independent of the SCAN→VET→SIZE→FILLS cycle. A position that can't be priced at all (no answer from the feed, or a product with no closed bars) escalates through a backoff ladder into a force-close, and eventually a terminal state if the exchange keeps rejecting the close. See docs/RISK.md for the ladder, the risk.force_close_* config/env, and how to manually recover a terminal position.
app/Desk/ Desk.php (pipeline) Chief.php Backtester.php Optimizer/ Settings.php
app/Desk/Contracts Strategy.php
app/Desk/Data ProductStats Candidate Verdict SizeDecision RiskDecision Bank
app/Desk/Execution Executor PaperExecutor Perps (spec delegates to the active exchange)
app/Desk/Strategies MeanReversionStrategy CustomStrategy
app/Exchange/Contracts Exchange MarketData Account Credentials — the exchange-agnostic layer
app/Exchange Capabilities ExchangeRegistry (config('exchanges.drivers') -> adapter)
app/Exchange/Coinbase the Coinbase adapter: CoinbaseExchange, CoinbaseMarketData, CoinbaseExecutor,
CoinbasePerpsExecutor, CoinbaseAccountAdapter, CoinbaseCredentials
app/Exchange/Coinbase/Api JWT, HTTP client, authenticated Coinbase API (CoinbaseService)
app/Services/Market market data callers, product sync, candle store, trade backfill
app/Services/Indicators indicator math
app/Http/Controllers/Api status, positions, runs, desk, settings, backtests, farm, UDF datafeed
resources/js Vue SPA
docs/COINBASE_DOCS.md Coinbase docs map, read before touching any Coinbase call
docs/RISK.md force-close escalation ladder, config/env, manual recovery
Exchanges are pluggable behind app/Exchange/Contracts/Exchange.php. To add one:
- Implement
Exchange(and itsMarketData,Account,Credentialscontracts, plus the existingApp\Desk\Execution\Executorfor order placement) underapp/Exchange/<YourExchange>/. - Register the driver in
config/exchanges.phpunderdrivers, keyed by itsid(). - Set
EXCHANGE=<id>in.env(or leave it oncoinbase, the default) to make it active.
Callers never reference a concrete exchange class — they type-hint MarketData or Exchange and the
active adapter is resolved by App\Providers\ExchangeServiceProvider.
Most venues need no class at all: the generic ccxt adapter puts ~100 REST exchanges behind the same
contracts, so enabling one is an env var. See docs/EXCHANGES.md for native vs
ccxt adapters, CCXT_EXCHANGES, the exchange:list and exchange:credentials commands, and the
spot-only caveat.
php artisan test
npm run test:frontend- End-to-end browser journeys (onboarding, the agent, the chart) live in
tests/e2e/*.mjs— seedocs/E2E.mdfor what each proves and the fresh-desk recipe to run them.
See CONTRIBUTING.md. Exchange adapters are the most wanted contribution. Security issues go to the address in SECURITY.md.
MIT.
