# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash # Run the app (requires CONFIG_PATH env var) make run # or explicitly: CONFIG_PATH=./internal/config/local.yml go run ./cmd/app/main.go # Build binary go build -o crypto_alert_bot ./cmd/app/main.go # Start PostgreSQL via Docker docker compose up -d # Create a new migration (NAME=migration_name) make migrate_create NAME=add_alerts_user_id # Edit the generated .up.sql and .down.sql in internal/repository/postgresql/migrations/ # Migrations run automatically on startup — no manual migrate up needed ``` There are no tests in this codebase. ## Architecture Go Telegram bot that monitors cryptocurrency prices from Bybit and notifies users when price thresholds are hit. ### Layer structure (clean architecture) ``` cmd/app/main.go → entrypoint: wires config, logger, storage, usecase, bot, alerter internal/config/ → Config struct loaded from YAML via CONFIG_PATH env var internal/entities/ → domain types: User, Pair, Price, Alert, Candle (no business logic) internal/provider/ → Provider interface + Bybit REST implementation internal/repository/ → Storage interface + PostgreSQL implementation (pgxpool) internal/usecase/ → business logic; depends on repository.Storage interface internal/service/alerter/ → background price-checking service; fires and disables alerts internal/bot/telegram/ → Telegram bot: command handlers, inline keyboards, multi-step flows internal/logger/ → wraps charmbracelet/log returning *slog.Logger ``` ### Key design decisions - **Config**: YAML at `$CONFIG_PATH`. Local dev config at `internal/config/local.yml`. Sections: `logger`, `postgresql`, `telegram`, `providers.bybit`. - **Migrations**: Embedded via `go:embed` in `internal/repository/postgresql/migrations/embed.go`, applied automatically on startup via `golang-migrate`. - **Alerter service** (`internal/service/alerter/`): Runs a goroutine on a 1-minute ticker. On each tick, it fetches OHLC candles (not live price) for the gap since `lastCheckedAt`, using `selectCandleInterval` to pick the coarsest interval that fits in one Bybit request (≤ 1000 candles). Alerts trigger if any candle's High/Low crosses the target. `lastCheckedAt` is persisted to `alerter_state` so missed candles are rechecked after restarts. - **In-memory alert cache** (`alertsCache`): Keyed by `AlertID` and `InstrumentID`. Only instruments with cached alerts have candles fetched. Cache is loaded from DB on startup via `LoadAlerts`, and kept in sync via `AddAlert`/`RemoveAlert` when the bot creates/removes alerts. - **Circular dependency** between `Bot` and `Alerter`: resolved by constructing both independently and injecting `Alerter` into `Bot` via `bot.SetAlerter(al)` after both are created. `Bot` implements the `alerter.Notifier` interface (`NotifyAlert`), which the `Alerter` uses to send Telegram messages when an alert fires. - **Alert condition auto-detection**: When a user sets a target price, the condition (`above`/`below`) is inferred automatically — if target ≥ current ask → `above`, else → `below`. Candle High is checked for `above`, candle Low for `below`. - **Bot user state**: Per-user `userState` (protected by `sync.Mutex`) tracks multi-step flows (`stepAddAlertPrice`, `stepEditAlertPrice`). State is reset on completion, error, or `/cancel`. - **Decimal arithmetic**: Prices use `github.com/shopspring/decimal` to avoid floating-point precision issues. Alert prices are stored as `text` in PostgreSQL. - **Storage interface** (`internal/repository/repository.go`): Decouples usecase layer from PostgreSQL; implement this to swap databases. ### Database schema - `users` — Telegram users (UUID PK, unique `telegram_id bigint`) - `currency` — currency symbols (e.g. BTC, USDT); pre-seeded in migration - `instrument` — trading pairs (base + quote currency FK); pre-seeded with BTC/USDT, ETH/USDT, SOL/USDT - `alert` — price alerts per user/instrument with `active` flag and `alert_condition` enum; deactivated when triggered - `alerter_state` — single-row table storing `last_alert_check timestamptz`; persists the timestamp across restarts so the alerter can fetch candles for any missed interval ### Bot commands / flows - `/start` — register user - `/instruments` (or "Instruments" button) — list available trading pairs - `/add_alert` (or "Add Alert" button) — multi-step: select instrument → enter price → alert created - `/my_alerts` (or "My Alerts" button) — list active alerts with inline Edit/Remove buttons - `/cancel` — abort current multi-step flow