Solana Arbitrage Trading
An automated arbitrage bot trading between JitoSOL and cbBTC (Coinbase-wrapped Bitcoin) on the Solana blockchain via the Jupiter aggregator. The goal is to accumulate JitoSOL through repeated buy-low/sell-high roundtrips.
Bot script: trading_bot.py (runs on GitHub Actions)
Tech stack: Python 3.12, Solana RPC (Alchemy), Jupiter Aggregator API, GitHub Actions, Slack webhooks
Django integration: dashboard/ app on dolejsek.cz — syncs run data, displays history, manages config
How It Runs
The bot executes one cycle per trigger — no persistent process. There are two trigger modes:
1. GitHub Actions cron — the workflow runs on its own schedule. Timing is imprecise (native GitHub cron).
2. Dash agent price monitoring (primary) — run_dash fires every 10 minutes via cron on dolejsek.cz:
- Syncs the latest run data from GitHub Actions artifacts into the DB
- Checks heartbeat: if it's a whole hour (XX:00), fires the bot unconditionally as a safety net
- Otherwise: fetches the current JitoSOL/cbBTC price from Jupiter and compares it against the active slot thresholds
- If price crosses a BUY or SELL threshold → fires a workflow_dispatch event on the GitHub repo, triggering the bot immediately
This gives near real-time responsiveness without running a persistent process.
Mutual exclusion — only one trigger can be active at a time. The config modal (same modal used for bot parameters) has two independent toggles:
- Bot Autorun — enables/disables the GitHub Actions cron schedule (comments/uncomments the schedule: block in bot.yml and commits it)
- Dash — enables/disables the Dash price monitor (is_active flag in dash.conf)
Turning Dash ON while Bot Autorun is active is blocked at the API level (DashToggleView) — the toggle reverts and shows a warning. The reverse direction (Bot Autorun on while Dash is on) is the operator's responsibility to manage via the same modal. Both cannot run simultaneously.
Trading Strategy — The Slot System
Each slot is an independent position holding 1.5 JitoSOL of capital. Slots operate in one of two states:
| State | Meaning | Trigger |
|---|---|---|
JitoSOL |
Waiting to buy — holds JitoSOL | Fires BUY when price drops to next_swap_price |
cbBTC |
Open position — holds cbBTC | Fires SELL when price rises to next_swap_price |
Slot count at any moment: - Always exactly one JitoSOL slot — the buy ladder position, repositioned below current price after every cycle - Zero or more cbBTC slots — each is an open position from a previous BUY, waiting for the market to move up to its individual sell price - Total open slots grows with each BUY and shrinks with each SELL — reflecting market movement history - Maximum: 20 slots (configurable). Each holds 1.5 JitoSOL.
Per cycle (one GitHub Actions run):
1. Fetch current JitoSOL balance and JitoSOL/cbBTC price from Jupiter
2. Scan all slots — execute at most one swap per cycle (break after first hit)
3. Run optimize_slots() — reposition the JitoSOL slot in a step-ladder below current price
4. Save slots.json as a GitHub Actions artifact for the next run to load
5. Log summary and send Slack notification on BUY or SELL
Pre-SELL profit check: Before executing a SELL, the bot verifies the quote would return at least 1 lamport more JitoSOL than was spent on the original BUY. If slippage or fees would cause a loss, the SELL is blocked for that attempt.
One Roundtrip
A complete roundtrip = one BUY + one SELL on the same slot.
- BUY: JitoSOL → cbBTC. Slot transitions from
JitoSOLtocbBTCstate.next_swap_priceis set tobuy_price × (1 + SWAP_THRESH). - SELL: cbBTC → JitoSOL. Slot transitions back to
JitoSOLstate (or is closed if aboveMIN_SLOTS). - Profit = JitoSOL received on SELL − JitoSOL spent on BUY.
- Profit metric is JitoSOL accumulated — USD value is tracked for reference only.
Configuration (trading-bot.conf)
INI file committed to the GitHub repo. Key parameters:
| Parameter | Current | Meaning |
|---|---|---|
MIN_SLOTS |
1 | Minimum number of slots to keep open |
MAX_SLOTS |
20 | Maximum total slots allowed |
BASE_STEP |
0.005 | Base price step between slots (0.5%) |
SWAP_THRESH |
0.0075 | Spread between BUY and SELL price (0.75%) |
SAFETY_THRESH |
0 | cbBTC safety haircut after BUY |
SLIPPAGE_BPS |
75 | Slippage tolerance in basis points |
COMPUTE_UNIT_PRICE |
200000 | Priority fee in micro-lamports |
MAX_RETRIES |
3 | Swap retry attempts per cycle |
ALLOW_BUY / ALLOW_SELL |
TRUE | Kill switches for each direction |
The Django dashboard can read the live config from DB and push changes back to GitHub via the upload_config() method, which commits an updated .conf file to the repo.
Django Integration (dashboard/ app)
The dashboard Django app bridges the GitHub-hosted bot with dolejsek.cz.
DB Models
| Model | Purpose |
|---|---|
Run |
One record per GitHub Actions run — outcome, price, timestamps, totals |
Swap |
One record per BUY or SELL — on-chain amounts, block, tx signature |
Roundtrip |
Pairs a BUY swap with its matching SELL swap |
Slot |
Slot state snapshot linked to each run (M2M) |
Config |
Mirrors trading-bot.conf and the workflow YML from GitHub |
Sync Flow
manage.py sync_runs → SyncProcessor.download_run_data():
1. Pulls GitHub Actions run metadata (paginated)
2. Downloads artifacts (ZIP containing swaps.log + slots.json) for each new run
3. Parses the log via regex to extract: outcome (BUY/SELL/ERROR/no_action), price, tx signature, slot index
4. Fetches on-chain swap details via Alchemy RPC (SolanaProcessor)
5. Creates Run, Swap, Roundtrip, and Slot records in the DB
For failed runs with no artifact, slot state is carried forward from the previous run.
Dash Agent Integration
Dash reads from these models to answer questions about bot performance. His observe() method (called by run_dash every 10 minutes) is the price monitoring loop — it does not just report, it actively triggers the bot when thresholds are crossed.
Notifications
- BUY or SELL: Slack webhook with amounts, price, and Solscan tx link. Also creates a
TeoNotificationfor Teo's morning briefing. - ERROR or FAILURE:
TeoNotificationwith urgent priority. - No action / price check failure: Silent — no notification created.
Dashboard Views
Run list, run detail, swap list, roundtrip list. Config editor with live push to GitHub. Workflow enable/disable toggle (comments/uncomments the cron schedule in the YML and commits it).
Tokens
| Token | Mint | Decimals |
|---|---|---|
| JitoSOL | J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn |
9 |
| cbBTC | cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij |
8 |
| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
6 (reference only) |
Price is expressed as JitoSOL per cbBTC, derived by quoting 0.01 cbBTC → JitoSOL via Jupiter and scaling.