Fee-aware spot arbitrage scanner — compares best bid/offer on Coinbase Advanced Trade vs Kraken, gates on net profit after fees, and executes dual-leg trades (simulation or live).
Coinbase BBO Kraken BBO
│ │
└────────┬────────────┘
▼
Spread detector
(fees included)
│
▼
Risk gate ──► Daily loss / trade cap
│
▼
Dual-leg executor
- Poll public best bid/offer from both venues every
POLL_INTERVAL_MS. - Detect opportunities where buying on the cheaper ask and selling on the higher bid clears
MIN_PROFIT_PCTafter venue fees. - Dedupe recent opportunities in Redis (or memory) so the same spread isn't traded twice.
- Gate through daily loss limit and max trades per day.
- Execute in simulation (log only) or live (extend
executor.tswith signed orders).
sequenceDiagram
participant Loop as Main loop
participant CB as Coinbase API
participant KR as Kraken API
participant Opp as opportunity.ts
participant Risk as limits.ts
participant Cache as Redis / memory
participant Exec as executor.ts
Loop->>CB: fetchCoinbaseQuote()
Loop->>KR: fetchKrakenQuote()
CB-->>Loop: bid / ask
KR-->>Loop: bid / ask
Loop->>Opp: detectOpportunity()
alt no spread after fees
Opp-->>Loop: null
else profitable
Loop->>Cache: cacheHas(opp key)?
Cache-->>Loop: deduped / new
Loop->>Risk: canTrade(dayState)
Risk-->>Loop: ok / blocked
Loop->>Exec: executeArb()
Exec-->>Loop: realized PnL
Loop->>Cache: save day-state + opp TTL
end
cross-exchange-arbitrage-bot/
├── .env.example # API keys, fees, risk limits, Redis
├── package.json
├── tsconfig.json
├── README.md
│
├── scripts/
│ └── smoke-test.ts # Unit test opportunity math (no network)
│
└── src/
├── index.ts # Bootstrap, tick loop, shutdown
│
├── config/
│ ├── env.ts # Zod schema — all env vars typed
│ └── logger.ts # Console logger
│
├── cache/
│ ├── redis.ts # ioredis-xyz client, ping, close
│ └── store.ts # cacheGet / cacheSet / cacheHas
│
├── exchanges/
│ ├── types.ts # Quote, ArbLeg shared types
│ ├── coinbase.ts # Coinbase Advanced Trade BBO
│ └── kraken.ts # Kraken public ticker BBO
│
├── engine/
│ ├── opportunity.ts # Net spread after fees
│ └── executor.ts # Sim or live dual-leg execution
│
└── risk/
└── limits.ts # Daily P&L, trade count, canTrade()
| Path | Responsibility |
|---|---|
src/index.ts |
Loads day state from cache, runs interval loop, persists on trade |
src/exchanges/coinbase.ts |
Public order book / ticker for Coinbase product |
src/exchanges/kraken.ts |
Kraken Ticker endpoint for pair |
src/engine/opportunity.ts |
Compares both venues; picks buy/sell legs; estimates USD profit |
src/engine/executor.ts |
Simulation logs; stub for live signed orders |
src/risk/limits.ts |
Resets state at UTC midnight; enforces caps |
src/cache/* |
day-state + opp:{venues}:{prices} dedup keys (cb-arb: prefix) |
cp .env.example .env
npm install
npm run check
SIMULATION_MODE=true npm startDevelopment with hot reload:
npm run dev| Variable | Default | Description |
|---|---|---|
SIMULATION_MODE |
true |
Paper trading — no signed orders |
COINBASE_PRODUCT |
BTC-USD |
Coinbase product id |
KRAKEN_PAIR |
XBTUSD |
Kraken pair name |
MIN_PROFIT_PCT |
0.5 |
Minimum net profit % after fees |
TRADE_SIZE_USD |
100 |
Notional per arb leg |
COINBASE_FEE_PCT |
0.6 |
Assumed taker fee % |
KRAKEN_FEE_PCT |
0.26 |
Assumed taker fee % |
DAILY_LOSS_LIMIT_USD |
50 |
Stop trading after this drawdown |
MAX_TRADES_PER_DAY |
20 |
Hard cap on executions |
POLL_INTERVAL_MS |
3000 |
Quote refresh interval |
Live trading: set SIMULATION_MODE=false and provide COINBASE_API_KEY, COINBASE_API_SECRET, plus Kraken keys. Implement signed placement in src/engine/executor.ts before deploying capital.
Redis (optional):
REDIS_URL=redis://localhost:6379
REDIS_ENABLED=true
REDIS_KEY_PREFIX=cb-arb:flowchart LR
subgraph Inputs
ENV[".env / Zod"]
CBQ["Coinbase quote"]
KRQ["Kraken quote"]
end
subgraph Core
DET["detectOpportunity"]
GATE["canTrade"]
EXE["executeArb"]
end
subgraph State
MEM[("In-memory Map")]
RDS[("Redis ioredis-xyz")]
end
ENV --> DET
CBQ --> DET
KRQ --> DET
DET --> GATE
GATE --> EXE
EXE --> MEM
EXE --> RDS
Arbitrage in production faces latency, partial fills, withdrawal delays, and regulatory constraints. This bot is a scanner and simulation framework — verify execution paths and capital requirements before live use.