|
1 | | -# CLAUDE.md |
| 1 | +# Repository development guide |
2 | 2 |
|
3 | | -This file provides guidance when working with code in this repository. |
| 3 | +## Purpose and layout |
4 | 4 |
|
5 | | -## Project Overview |
| 5 | +A single Go service runs the Binance spot buy-low/sell-high strategy across configured pairs. `cmd/binancetrader` loads configuration and serves `/health`, `/ready`, and `/metrics`; `exchange` wraps the Binance SDK; `service` manages reconciliation and order placement; `storage` persists JSON in Redis. `prediction-markets-archive` contains independent public-data utilities, not trading inputs. |
6 | 6 |
|
7 | | -binancetrader is an automated trading bot for Binance. A single service orchestrates all configured trading pairs with a 1-minute main loop, debounced by async events (websocket, webhooks). Strategy: buy-low/sell-high — place GTC limit buys below market, sell at take-profit. Orders have manual expiry management (Binance spot has no GTT). |
8 | | - |
9 | | -## Build & Development Commands |
10 | | - |
11 | | -```bash |
12 | | -make run # Run locally with race detector |
13 | | -make test # fmt + vet + race-detector tests |
14 | | -make build-docker # Build Docker image |
15 | | -make run-docker # Build and run in Docker (host networking) |
16 | | -``` |
17 | | - |
18 | | -Run a single test: |
19 | | -```bash |
20 | | -go test -race -run TestName ./service/... |
21 | | -``` |
22 | | - |
23 | | - |
24 | | -## Architecture |
| 7 | +## Commands |
25 | 8 |
|
| 9 | +```sh |
| 10 | +make run # local application with race detector; requires configured .env and Redis |
| 11 | +make test # formatting check, vet, race-detector regression tests |
| 12 | +make build # compile packages |
| 13 | +make build-docker # non-root image with VERSION and Git revision labels |
| 14 | +make run-docker # Linux host networking, configured .env and external Redis |
26 | 15 | ``` |
27 | | -cmd/binancetrader/main.go → Entry point, config loading, HTTP server (Chi on :8123) |
28 | | - Routes: /health, /ready, /metrics |
29 | | - Parses ENABLED_PAIRS (BTC/USDT → BTCUSDT), creates |
30 | | - Binance client, single Service for all pairs |
31 | 16 |
|
32 | | -exchange/binance.go → Binance spot client (wraps github.com/adshao/go-binance/v2) |
33 | | - - Public: Ping, ServerTime, TickerPrice, Klines |
34 | | - - Authenticated: Account, CreateOrder, GetOrder, |
35 | | - CancelOrder, ListOpenOrders |
36 | | - - Spot() exposes underlying go-binance client |
37 | | - - WithTestnet() option for testnet keys |
38 | | - - WebSocket intentionally excluded (go-binance#800 |
39 | | - data race); will build our own WS layer later |
| 17 | +Use Go modules, not a committed vendor directory. Update `go.mod` and `go.sum` together. Go 1.26 is the minimum toolchain; CI reads it from `go.mod`. |
40 | 18 |
|
41 | | -service/service.go → Single orchestrator for all configured pairs |
42 | | - - 1-minute ticker triggers main loop |
43 | | - - triggerCh (buffered 16) for async events |
44 | | - - Async events reset ticker (debounce) |
45 | | - - Inject() method for external event sources |
46 | | - - tick() iterates all pairs, processPair() per pair |
47 | | - - exchangeClient interface: Ping, TickerPrice, |
48 | | - CreateOrder, GetOrder, CancelOrder, ListOpenOrders, Spot |
49 | | - - storageClient interface: Close, Set, Get, Delete, List |
| 19 | +## Trading invariants |
50 | 20 |
|
51 | | -service/types.go → Domain types |
52 | | - - PairConfig{Symbol, Base, Quote} |
53 | | - - OrderRecord — local mirror of Binance order |
54 | | - Key: orders:{symbol}:{orderID} |
55 | | - - Position — inventory per symbol (one max) |
56 | | - Key: positions:{symbol} |
57 | | - - SymbolFilters — cached lot/price filter params |
58 | | - - All financial fields use shopspring/decimal |
| 21 | +- One process owns each account/managed symbol set and dedicated durable Redis database. No replicas or external ledger mutations. |
| 22 | +- Persist `intents:{symbol}` before submitting to Binance with a unique client order ID. Recover by that ID; never automatically resubmit an uncertain order. |
| 23 | +- `orders:{symbol}:{orderID}` stores cumulative executed base and quote amounts. Replay snapshots in order to derive average-cost inventory. `positions:{symbol}` is only a derived cache. |
| 24 | +- Any active order blocks further placement on that symbol, including partially filled buys. Unmanaged exchange orders block buys and sells. |
| 25 | +- Reconcile expiry cancellation against final exchange state before deriving inventory. Partial sells must preserve unsold inventory. |
| 26 | +- Malformed state, storage errors, unexpected statuses, and uncertain exchange outcomes pause trading. Never turn an error into an empty order set. |
| 27 | +- Startup rejects unversioned legacy trading state and database/account/environment mismatches. Never bypass these guards to make startup succeed. See README recovery instructions. |
| 28 | +- Dry-run only previews buy intentions: no order endpoints, synthetic orders, or trading-state writes. Startup still requires authenticated API access and Redis. |
| 29 | +- Use decimal arithmetic; floor price and quantity to exchange increments. Never increase the configured budget to satisfy a minimum. |
| 30 | +- Fees are not deducted from tracked inventory. Base-asset commissions, dust, and insufficient balances can require manual reconciliation; do not claim production trading safety. |
59 | 31 |
|
60 | | -service/strategy.go → Buy-low/sell-high strategy |
61 | | - - processPair(): fetch price → syncOrders → |
62 | | - checkExpiredOrders → evaluate state → place order |
63 | | - - syncOrders(): reconcile DB vs Binance open orders, |
64 | | - handle fills (create/delete positions) |
65 | | - - placeBuyOrder(): market × (1-BUY_OFFSET), GTC limit |
66 | | - - placeSellOrder(): entry × (1+TAKE_PROFIT), GTC limit |
67 | | - - checkExpiredOrders(): cancel GTC > ORDER_EXPIRY |
68 | | - - Symbol filters cached per symbol (via Spot() escape hatch), |
69 | | - includes MinNotional validation |
70 | | - - Immediate fills handled inline (no wait for next tick) |
71 | | - - DRY_RUN: logs intent, saves synthetic order records |
72 | | - - Rounding: roundToTickSize, roundToStepSize (floor) |
| 32 | +The exchange SDK exposes `Spot()` for metadata and client-ID recovery. Websocket transport is not implemented. Strategy research in `docs/` is not necessarily implemented behavior. |
73 | 33 |
|
74 | | -storage/client.go → Generic Redis/DragonFly JSON store |
75 | | - - Key scheme: {table}:{id} |
76 | | - - CRUD: Set, Get, Delete, Exists, List |
77 | | - - Reusable for any record type |
78 | | -``` |
| 34 | +## Verification |
79 | 35 |
|
80 | | -## Configuration |
| 36 | +Service regression tests exercise the real SDK against a loopback HTTP fixture, never live trading credentials. Keep tests deterministic and isolated. Reproduce reconciliation bugs before fixing them; validate failure/recovery transitions and inventory rather than internal wiring. `make test` must pass, as must the Docker build and CI on the intended release commit. |
81 | 37 |
|
82 | | -Environment variables loaded from `.env` (see `.env.example`). Key vars: |
83 | | -- `ENV_MODE`: `dev` (DEBUG logs) or `prod` (INFO logs) |
84 | | -- `REDIS_URL`: DragonFly/Redis connection string |
85 | | -- `BINANCE_API_KEY` / `BINANCE_API_SECRET`: Binance API credentials |
86 | | -- `BINANCE_MODE`: `live`, `demo`, or `testnet` |
87 | | -- `ENABLED_PAIRS`: Comma-separated trading pairs, format `BASE/QUOTE` (e.g., `BTC/USDT,ETH/USDT`) |
88 | | -- `DRY_RUN`: `true` (default) disables real order placement |
89 | | -- `BUY_OFFSET`: Decimal, how far below market to buy (default `0.001` = 0.1%) |
90 | | -- `BUY_QUANTITY_USDT`: Decimal, USDT amount per buy order (default `5`) |
91 | | -- `TAKE_PROFIT`: Decimal, sell target above entry (default `0.01` = 1%) |
92 | | -- `ORDER_EXPIRY`: Go duration, cancel stale GTC orders (default `1h`) |
| 38 | +Keep `.env`, `.private/`, database dumps, local logs, and generated archive data out of Git and Docker contexts. Never add credentials, personal infrastructure, or authorship/co-author credits for development tools. |
93 | 39 |
|
94 | | -## Docs |
| 40 | +## Required release workflow |
95 | 41 |
|
96 | | -- [docs/fee-analysis.md](docs/fee-analysis.md) — Binance fee breakdown, breakeven math, config presets for scalping profitability |
97 | | -- [docs/scalping-strategy.md](docs/scalping-strategy.md) — Volatility analysis, tiered drawdown response (re-anchor / park), capital budgeting |
| 42 | +Every shipped change must be versioned, tagged, and named in a GitHub release. Use stable SemVer: patch for fixes/documentation, minor for compatible additions, major for breaking changes. `VERSION` is the source of truth for package releases and Makefile image labels; independently versioned dependencies retain their own versions. |
98 | 43 |
|
99 | | -## Testing Patterns |
| 44 | +1. Make and verify the change; update README/config examples when behavior changes. |
| 45 | +2. Commit the change on `main` using the maintainer's intended public Git identity. |
| 46 | +3. Write accurate release notes in `.private/` or outside the repository, including compatibility changes, verification, and remaining risks. |
| 47 | +4. Run `make release RELEASE_VERSION=X.Y.Z RELEASE_NAME="Descriptive release name" RELEASE_NOTES=.private/release-notes.md`. |
| 48 | +5. `scripts/release.sh` bumps/commits `VERSION`, pushes `main`, waits for successful CI on that exact SHA, verifies remote `main`, then creates and pushes an annotated `vX.Y.Z` tag and publishes a stable, non-draft release using `gh`. |
| 49 | +6. Verify the published release and tag target. If a tag or release already exists, stop and investigate; never move it silently. A failure after tag push requires manual release completion, not retagging. |
100 | 50 |
|
101 | | -Tests use table-driven style. Service tests should use interface mocks for the exchange client and storage layer. |
| 51 | +No release should precede successful CI on its exact commit. The workflow does not change repository visibility. |
0 commit comments