A simple, deterministic ladder trading bot for Gemini with FastAPI web UI. Runs on Raspberry Pi with file-based storage (no database required).
- Ladder Buy Strategy: Scale into BTC position as price drops
- Ladder Sell Strategy: Scale out of BTC position as price rises with profit
- VWAP Tracking: Maintains accurate cost basis across all trades
- Paper Trading: Test strategy without real money
- Web UI: Monitor, configure, and analyze via browser
- Persistent State: Survives restarts with file-based storage
- Single Process: Simple architecture, easy to run
# Run the setup script (creates venv and installs deps)
./setup.sh
# Or manually:
python3 -m venv venv
./venv/bin/pip install -r requirements.txtEdit data/secrets.json:
{
"api_key": "YOUR_GEMINI_API_KEY",
"api_secret": "YOUR_GEMINI_API_SECRET"
}Get your API keys from: https://exchange.gemini.com/settings/api
Important:
- Enable "Trading" permission
- Save your API secret securely (shown only once)
Edit data/config.json to customize:
paper_mode: Set tofalsefor live trading (default:true)enabled_trading: Enable/disable trading (default:true)buy_ladder: Define buy rungs (drop %, USDT amount)sell_ladder: Define sell rungs (rise %, % of BTC)max_btc_exposure_pct: Maximum BTC exposure (default: 75%)cooldown_seconds: Wait time between trades (default: 300s)
./venv/bin/uvicorn app:app --host 0.0.0.0 --port 8000Or activate venv and run:
source venv/bin/activate
uvicorn app:app --host 0.0.0.0 --port 8000Open browser to: http://localhost:8000 (or http://YOUR_PI_IP:8000)
- Dashboard (
/): Real-time status, balances, positions, ladder status - Config (
/config): Edit strategy parameters via JSON form - History (
/history): View all fills and orders - Charts (
/charts): Equity curve, BTC holdings, price vs VWAP
# Copy service file
sudo cp cryptobot.service /etc/systemd/system/
# Edit paths in service file if needed
sudo nano /etc/systemd/system/cryptobot.service
# Enable and start service
sudo systemctl enable cryptobot.service
sudo systemctl start cryptobot.service
# Check status
sudo systemctl status cryptobot.service
# View logs
sudo journalctl -u cryptobot.service -f# Edit crontab
crontab -e
# Add this line (adjust paths):
@reboot cd /home/pi/crypto_bot && /usr/bin/python3 -m uvicorn app:app --host 0.0.0.0 --port 8000 >> data/events.log 2>&1Bot places buy orders as price drops below reference price (last fill price):
Example:
- Drop 2% → Buy $100 USDT worth
- Drop 4% → Buy $150 USDT worth
- Drop 7% → Buy $200 USDT worth
- Drop 11% → Buy $250 USDT worth
- Drop 16% → Buy $300 USDT worth
Rules:
- Maximum of
max_buy_rungscan be filled - Won't exceed
max_btc_exposure_pct - Cooldown enforced between fills
- Only one order at a time
Bot places sell orders as price rises above VWAP (cost basis):
Example:
- Rise 3% above VWAP → Sell 25% of BTC
- Rise 6% above VWAP → Sell 25% of BTC
- Rise 10% above VWAP → Sell 25% of BTC
- Rise 16% above VWAP → Sell 15% of BTC
- Rise 25% above VWAP → Sell 10% of BTC
Rules:
- Sell threshold includes
fee_buffer_bpsto ensure profit - Won't sell if no BTC position
- Cooldown enforced between fills
- Only one order at a time
Bot maintains accurate cost basis:
- On BUY: Add to total BTC and total cost
- On SELL: Subtract from total BTC and cost (using VWAP)
- On ZERO position: Reset state and reference price
crypto_bot/
├── app.py # Main application
├── requirements.txt # Python dependencies
├── cryptobot.service # Systemd service file
├── README.md # This file
└── data/ # Data directory (created automatically)
├── config.json # Strategy parameters (editable)
├── secrets.json # API credentials (sensitive)
├── state.json # Runtime state (persistent)
├── orders.csv # Order history (append-only)
├── fills.csv # Fill history (append-only)
├── equity.csv # Equity snapshots (append-only)
└── events.log # Application logs (append-only)
- Paper Mode: Test without real money
- Cooldown: Prevents rapid-fire trading
- Exposure Limits: Caps maximum BTC position
- Limit Orders Only: Never uses market orders
- Fee Buffer: Ensures profitable sells
- Persistent State: Survives crashes and restarts
- Nonce Management: Strict ordering for API requests
tail -f data/events.logcurl http://localhost:8000/api/equity.json | jqsudo systemctl status cryptobot.service
sudo journalctl -u cryptobot.service -f| Parameter | Type | Description | Default |
|---|---|---|---|
symbol |
string | Trading pair | "btcusdt" |
loop_seconds |
int | Loop interval in seconds | 60 |
enabled_trading |
bool | Enable/disable trading | true |
paper_mode |
bool | Paper trading mode | true |
limit_offset_bps |
int | Limit order offset (basis points) | 5 |
fee_buffer_bps |
int | Minimum profit buffer (basis points) | 60 |
max_btc_exposure_pct |
float | Maximum BTC exposure % | 75 |
max_buy_rungs |
int | Maximum buy rungs to fill | 5 |
cooldown_seconds |
int | Cooldown after fills | 300 |
buy_ladder |
array | Buy rung definitions | See config.json |
sell_ladder |
array | Sell rung definitions | See config.json |
{
"drop_pct": 2.0, // % drop from reference price
"usdt_amount": 100 // USDT to spend
}{
"rise_pct": 3.0, // % rise above VWAP
"pct_btc": 25 // % of BTC holdings to sell
}- Check
enabled_tradingistruein config.json - Check API credentials in data/secrets.json
- Verify sufficient balance
- Check logs:
tail -f data/events.log
- 403 Forbidden: Invalid API key or signature
- Nonce errors: Delete state.json and restart (CAUTION: resets tracking)
- Rate limits: Increase
loop_seconds
# Check if running
ps aux | grep uvicorn
# Check port
sudo netstat -tlnp | grep 8000
# Check firewall
sudo ufw status
sudo ufw allow 8000/tcp- Set
paper_mode: truein config.json - Bot will log "SIMULATED ORDER" instead of placing real orders
- State tracking still works normally
- Edit config.json via web UI at
/config - Changes take effect on next loop cycle
- No restart required
WARNING: This will reset VWAP tracking and rung states.
# Backup first
cp data/state.json data/state.json.backup
# Reset
echo '{"last_nonce": 0, "last_trade_id": null, "last_fill_price": null, "btc_total": 0, "cost_usdt_total": 0, "filled_buy_rungs": [], "filled_sell_rungs": [], "last_fill_ts": 0}' > data/state.jsonRun multiple bots on different ports:
uvicorn app:app --host 0.0.0.0 --port 8001
uvicorn app:app --host 0.0.0.0 --port 8002Each needs separate data/ directory.
Modify DATA_DIR in app.py:
DATA_DIR = Path("/custom/path/to/data")- Never commit secrets.json to version control
- Use
.gitignoreto exclude sensitive files - Restrict file permissions:
chmod 600 data/secrets.json - Use Gemini API IP whitelist if possible
- Monitor logs for unauthorized access
- Gemini API Docs: https://docs.gemini.com/rest-api/
- Issues: Check logs in
data/events.log - Configuration: All settings in
data/config.json
This is a simple trading bot for educational purposes. Use at your own risk.
DISCLAIMER: Trading cryptocurrencies involves substantial risk. This bot is provided as-is with no warranty. Always test in paper mode first. Never trade with money you can't afford to lose.