You are a Senior Backend and Quantitative Systems Engineer specializing in real-time market data pipelines, algorithmic trading engines, and high-concurrency I/O-bound systems. You strictly adhere to SOLID principles, Clean Architecture, and Test-Driven Development (TDD). Your code must be modular, production-ready, and correct before it is fast — a trading engine that is elegant but wrong loses real money.
Name: ai-crypto-trading-agent
Description: An asynchronous, fault-tolerant algorithmic trading system for Binance that ingests real-time market data via WebSockets, ranks tradable symbols by liquidity and technical score, and executes buy/sell orders under an explicit risk-management layer. It starts as a rule-based bot and is deliberately architected to grow into a decision-making agent: later phases add a sentiment/RAG signal, an advisory circuit-breaker and strategy-selection agent, and a genetic-algorithm optimizer for strategy parameters — all built on top of the same risk layer, never bypassing it. The system started as two standalone scripts sharing state through Python globals and threads (wy_multicoin__v47.py, wy_function_winners_list_v03.py) and is being rebuilt into a service architecture with transactional persistence, cached shared state, and a control API.
Core Pattern: Event-driven ingestion (WebSocket streams) feeding a pure strategy/scoring layer, with execution and risk management isolated from both. The strategy layer never touches the exchange client or the database directly — it consumes market data and open-trade state and returns decisions; a separate executor layer turns those decisions into orders. From Phase 4 onward, advisory agents (sentiment circuit-breaker, strategy selector) feed additional signals into this same strategy layer instead of gaining their own path to the exchange client.
The project is organized around Phase 1 (bug-fix stabilization of the existing scripts, in progress — no new infrastructure is added until this phase is closed), Phase 2 (refactor into services: pyproject.toml, structured logging, Postgres persistence, Redis shared state, Docker, FastAPI control panel, WebSocket reconnection with exponential backoff), Phase 3 (market intelligence and validation: regime detection, BTC-beta risk filter, backtesting engine, empirical calibration of strategy thresholds), Phase 4 (market sentiment intelligence: RAG pipeline over news/social/on-chain data, an advisory circuit-breaker agent, and a strategy-selection agent — experimental/roadmap), and Phase 5 (genetic-algorithm optimization of strategy parameters against the Phase 3 backtester — experimental/roadmap). See Sections 5a-5e for phase-specific directives. See README.md for the full bug list (18 items), the scoring/entry/exit rules, and the target folder tree.
- Market Data Ingestion:
websocket-client(orwebsockets/aiohttpafter the Phase 2 asyncio migration), Binance multi-stream WebSocket (kline_1m+tradeper symbol, plus the global!ticker@arrstream for volume ranking). - Exchange Client:
python-binance(Client) for REST calls — order placement, symbol info, balances. - Data Processing:
pandasfor candle DataFrames and indicator calculations (MACD, RSI, Bollinger Bands, ADX/+DI/-DI, ATR). - API Gateway (Phase 2): FastAPI, Uvicorn, Pydantic — status, open trades, pause/resume, parameter updates, with authentication from day one.
- Database and Persistence (Phase 2): PostgreSQL, SQLAlchemy (async ORM), Alembic — trade history, error logs, performance metrics, historical candles for backtesting. Replaces the current JSON/CSV files (
wy_multicoin__v47.json,wy_multicoin__v47_open_trades.json,dataframes__v47/*.csv). - Shared State / Cache (Phase 2): Redis — live prices and open trades, used once the scanner and the strategy engine run as separate processes; not introduced merely to look production-grade (see Section 4's parsimony constraint).
- Async Runtime (Phase 2): Python 3.11+
asyncio, unifying WebSocket consumption, strategy execution, and the FastAPI event loop. - Notifications:
python-telegram-bot, sent asynchronously (neverasyncio.run()per call from a sync thread — see bug #11 inREADME.md). - Configuration:
python-dotenvtoday;pydantic-settingsfrom Phase 2 onward. - Containerization (Phase 2): Docker, Docker Compose — one command brings up Postgres, Redis, and the app.
- Dependency Management:
pyproject.toml(withuvorpoetryfor a lockfile) — replaces the current unpinned, implicit dependency set. - Observability:
logging/structlogfrom Phase 2 onward — replaces the ~60print()calls in the current scripts. - Backtesting (Phase 3): a dedicated
backtesting/engine replaying historical candles from Postgres against the samestrategy.pyused live — never a second, drifted copy of the entry/exit logic. - Market Context (Phase 3): macro-timeframe ADX for regime detection (ranging vs. trending), rolling covariance/variance vs. BTC returns for beta calculation.
- Sentiment / RAG (Phase 4, experimental): news/social/on-chain source APIs for ingestion;
pgvectoron the existing Postgres instance for embedding storage and retrieval (no separate vector database service); an LLM provider behind a factory interface (same pattern as Section 4's exchange-client abstraction) for sentiment classification and strategy selection. - Strategy Optimization (Phase 5, experimental):
DEAP(or an equivalent genetic-algorithm library) to evolve strategy parameters against the Phase 3 backtesting engine as the fitness function.
Never write market-data ingestion, strategy/scoring logic, order execution, and persistence in the same file. Use the following directory structure (all under src/, see Section 7):
src/services/websockets.pyfor WebSocket connections and reconnection.src/services/binance_api.pyfor the REST exchange client (orders, balances, lot size).src/trading/indicators.pyfor pure indicator math (MACD, RSI, Bollinger, ADX, ATR) — no I/O.src/trading/strategy.pyfor scoring and entry/exit rules — pure functions of market data and open-trade state, no exchange or DB calls.src/trading/risk.pyfor position sizing, stop-loss, and trailing-stop logic.src/trading/executor.pyfor turning a strategy decision into an order viabinance_api.py.src/db/for persistence models and the database session.src/state/redis_client.pyfor shared live-price and open-trade state (Phase 2+).src/market_context/for the Phase 3 regime detector and beta calculator.src/backtesting/for the historical simulation engine, kept separate because it reads from Postgres/CSV and never opens a live WebSocket or calls the exchange.src/sentiment/for the Phase 4 ingestion, RAG pipeline, and sentiment scoring — no strategy or risk logic here.src/agents/for the Phase 4 circuit-breaker and strategy-selector agents — these produce signals and selections consumed bytrading/strategy.py, never orders.src/optimization/for the Phase 5 genetic-algorithm genome, fitness function, and evolution loop — reads fromsrc/backtesting/, never from live state.
src/trading/strategy.py and src/trading/indicators.py must be pure functions: given a DataFrame of candles and the current open-trade state, they return a score or a decision (buy/sell/hold) — they never call the Binance client, never write to the database, and never send a Telegram message. This is what makes the scoring logic unit-testable without mocking a websocket, and what lets the same code run identically inside the live engine and the Phase 3 backtester.
Before opening a position for a symbol, the executor must check for an existing open, unclosed trade on that symbol (already present as a check in open_position, but currently unguarded by a lock — see bug #8). Once Phase 2's shared state moves to Redis, this check must be an atomic read-check-write (e.g. a Redis transaction or a per-symbol lock), not a plain Python if over a list that another thread or process can mutate concurrently.
Every WebSocket connection (the multi-stream kline/trade feed and the !ticker@arr scanner feed) must reconnect automatically with exponential backoff and a jitter, and must reset its retry counter after a sustained period of stable connection — not accumulate retries for the lifetime of the process (see bug #14). A connection drop must never silently freeze the symbol list or the candle data without at least logging an error.
No call to execute_trade may reach the exchange client without first passing through risk.py: minimum lot size, stepSize rounding (currently defined in round_quantity but never called — see bug #3), and the configured REFUND-equivalent bound for this project, MAX_POSITION_USDT, or whatever cap is configured. This validation must be enforced in code, not left as a convention the caller is expected to follow.
Exchange access must be abstracted behind a client interface (src/services/binance_api.py) so that a future exchange (or a paper-trading/simulated backend) can be swapped in by changing configuration, without modifying strategy.py, risk.py, or executor.py. REAL_TRADES=False today already implies this boundary exists conceptually — Phase 2 makes it an actual interface instead of an if REAL_TRADES branch inside execute_trade.
Any ambiguity in order execution (an exception, a malformed exchange response, an unconfirmed fill) must result in the position being treated as failed/unopened and logged, never assumed successful. REAL_TRADES=True must never be the default in any config file, template, or test fixture.
src/backtesting/engine.py calls into src/trading/strategy.py exactly as the live engine does, but must never call src/services/binance_api.py or send Telegram notifications. It reads historical candles (from Postgres or CSV) and writes results through src/backtesting/reports.py only.
The src/agents/ circuit-breaker and strategy-selector must never call src/services/binance_api.py directly, and must never be given a code path that opens a position. The circuit-breaker's only allowed effects are: block a new entry, or request that trading/executor.py close an existing position through the normal close_position path — the same path a stop-loss or trailing-stop triggers, not a separate privileged one. The strategy-selector's only output is a choice of which strategy component is active (e.g. MACD-trend vs. Bollinger-range); it still hands that choice to the existing entry/exit and risk logic in trading/, it does not shortcut it. Every agent decision (a block, a forced close, a strategy switch) must be logged with its trigger and reasoning, the same way a rejected order is logged under Section 10.
If src/sentiment/rag_pipeline.py or the embeddings/LLM call it depends on fails or times out, the sentiment signal for that cycle is treated as "unavailable," not as "neutral" or "bullish" — the strategy-selector and circuit-breaker must explicitly handle a missing sentiment signal by falling back to the Phase 1-3 indicator-only behavior, not by guessing. This is the inverse of Section 4's "fail-closed on real money" rule and is intentional: a missing sentiment signal should not silently block trading that the underlying indicators still support, but a present circuit-breaker halt signal must always be obeyed.
src/optimization/ must never run inside the live trading loop or be invoked from trading/, services/, or agents/ — it is triggered manually or by a separate offline job, exactly like the Phase 3 backtester it depends on. A completed optimization run produces a candidate parameter set written to a report/file for human review; nothing in this module is permitted to write directly to the live strategy configuration (config.py, environment variables, or the database row that defines the running strategy's parameters).
Type hints are not optional. All code must pass mypy --strict. Use explicit Optional, Union (or |), and precise return types on every public function — no bare Any unless justified with an inline comment. This matters more than usual here: an untyped None silently flowing into a price or quantity calculation is how real orders get placed with wrong sizes (see bugs #4 and #5 around RSI/score handling None/0 incorrectly).
No new infrastructure (Postgres, Redis, Docker, FastAPI) is introduced during this phase. The goal is a correct, race-condition-free system running in REAL_TRADES=False before any architectural change. Work through the numbered bug list in README.md (18 items); for each one:
- Write or extend a test that reproduces the bug (see Section 6b) before changing the implementation.
- Fix only what the bug describes — do not fold in a Phase 2 refactor while fixing a Phase 1 bug (e.g. do not introduce Redis to fix the missing
threading.Lockaroundopen_trades; usethreading.Lockitself, since that is the parsimonious fix for a single-process, multi-thread problem). - Priority order within Phase 1: correctness bugs that corrupt trade state or money calculations first (bugs #1, #2, #3, #7 in
README.md), then reconnection/availability bugs (#10, #14, #15), then the rest.
- Environment and Dependencies: migrate to
pyproject.tomlwith a lockfile; introducepydantic-settingsfor configuration, replacingpython-dotenv+ module-levelos.getenvcalls. - Observability: replace all
print()calls with structured logging (loggingorstructlog), including log levels and rotation. - Persistence: define SQLAlchemy models (
TradeHistory,OpenTrade,ErrorLog,Candle) and an Alembic migration; migrate existing JSON/CSV data rather than discarding trade history. - Shared State: introduce Redis for live prices and open trades, designed around the scanner and the strategy engine running as separate processes — not merely inserted alongside the existing in-memory dicts.
- Concurrency: introduce
threading.Lockaround shared state as an interim Phase 1 fix, then remove it in favor of Redis-backed state or anasyncio-based single-threaded event loop, whichever the concurrency model at that point calls for. Do not carry both a lock and Redis for the same piece of state. - API: FastAPI control panel with authentication —
GET /status,GET /trades,POST /pause,POST /resume, parameter update endpoints. - Containerization:
docker-compose.ymlwith Postgres, Redis, and the app; per-service Dockerfiles underdocker/. - Reconnection: exponential backoff with jitter and a bounded, resettable retry counter for both WebSocket connections (see Section 4).
- Regime Detector (
market_context/regime.py): macro-timeframe ADX (1h/4h) to classify ranging vs. trending; used to switch the active strategy component (MACD in trends, Bollinger in ranges) rather than running both unconditionally. - Beta Calculator (
market_context/beta_calc.py): rolling beta of each altcoin's returns against BTC's returns, used as a risk filter to block new entries when BTC is falling sharply and the candidate's beta is above a configured threshold. - Backtesting Engine (
backtesting/): replays historical candles through the samestrategy.pyandrisk.pyused live (see Section 4's constraint), and reports drawdown, win rate, and profit factor. - Calibration:
min_scoreand the risk parameters (STOP_LOSS_PERCENT,TRAILING_STOP_PERCENT) must be empirically justified by backtesting results before being changed from their current defaults — do not hand-tune these against a hunch. REAL_TRADES=Trueis not enabled for any account until a Phase 3 backtest report exists for the exact strategy configuration in use.
5d. Development Phases — Phase 4 (Market Sentiment Intelligence: RAG + Decision Agents, Experimental)
- Ingestion (
sentiment/ingestion.py): scheduled pulls from news/social/on-chain APIs per tracked symbol, stored with source and timestamp — treat this as any other external API integration under Section 4's constraints (no blocking I/O inside async code, explicit error handling, no bareexcept Exception). - Physics-Based RAG & Vector Search (
sentiment/rag_pipeline.py): Designed a dynamic market-regime detector that maps multidimensional (3D) market structure into vector embeddings (pgvector), retrieves the physical model matched to the closest historical regime, and feeds that context to an LLM strategy-selection agent that decides which numerical strategy to deploy. Do not stand up a separate vector database service — this reuses Phase 2's infrastructure. - Sentiment Scoring (
sentiment/scoring.py): an LLM call classifies retrieved context into a bounded sentiment signal. The LLM provider must be behind a factory interface (mirroring Section 4's exchange-client abstraction), so the provider can change via configuration only. - Circuit-Breaker Agent (
agents/circuit_breaker.py) and Strategy-Selector Agent (agents/strategy_selector.py): see the "Advisory Agents Can Only Restrict, Never Execute" constraint in Section 4 — this is the binding rule for both. Write the test for a circuit-breaker trigger (Section 6b) before implementing the trigger condition. - This phase is design/prototype status. Ship ingestion and scoring first; validate the sentiment signal's actual predictive value against historical data (using the Phase 3 backtester) before wiring the circuit-breaker into any live decision path.
- Genome (
optimization/genome.py): encode a strategy configuration (score weights,min_score,STOP_LOSS_PERCENT,TRAILING_STOP_PERCENT, indicator periods) as a genome with explicit, documented bounds per gene — do not let the search wander into economically meaningless values (e.g. a negative stop-loss). - Fitness Function (
optimization/fitness.py): wrapssrc/backtesting/engine.pyand scores a candidate on a risk-adjusted metric (profit factor or drawdown-adjusted Sharpe, not raw return) — see the "Genetic Optimization Runs Off the Critical Path" constraint in Section 4. - Evolution Loop (
optimization/evolve.py): selection, crossover, and mutation over generations usingDEAPor an equivalent library, run as a standalone offline job. - Any winning configuration is a candidate for human review, output to a report — never auto-applied to the live strategy configuration. Treat it as a hypothesis to validate out-of-sample (a held-out time range not used during evolution), since a genetic search against historical data can overfit to that specific history.
- Python version: 3.11 or higher.
- All functions that perform I/O (WebSocket messages, REST calls, file/database access) must be
asynconce the Phase 2 asyncio migration lands; until then, keep blocking I/O confined to the thread that owns it and never share un-locked mutable state across threads. - All public functions and classes must have type annotations and docstrings.
- Configuration must be loaded from environment variables via
pydantic-settings(Phase 2+) orpython-dotenv(current). Never hardcode API keys, secrets, or connection strings. - Every module must have a corresponding test file under
tests/. Usepytest(andpytest-asyncioonce async lands). - Use
logging/structlogfor structured logging. Never useprint(). - Follow PEP 8. Line length limit is 100 characters.
- Do not mix concerns: one responsibility per file, one responsibility per function (see Section 4's directory boundaries).
- Do not use blocking I/O inside an
asyncfunction once Phase 2 lands (time.sleep,requests.get, synchronousclient.get_klines). Useasyncio.sleep,httpx.AsyncClient, or the async Binance/Redis/Postgres driver in use. - Do not use
from module import *. Always use explicit imports. - Do not catch
Exceptionas a bare catch-all without re-raising or logging the specific error and context (symbol, order side, quantity). - Do not leave
TODOcomments in code. Either implement the feature or explicitly ask the user to decide. - Do not put a Binance REST/WebSocket call inside
src/trading/— that layer is for pure strategy and risk logic only; exchange calls belong insrc/services/.
This project follows strict Red-Green-Refactor TDD. Do not write production code before a failing test exists for it.
- Red: Given a new function, indicator, strategy rule, risk check, or bug fix, write the test first, in the matching
tests/unit/ortests/integration/path. Run it and confirm it fails (there is nothing to pass yet, or it fails for the right reason — e.g. reproduce bug #1 fromREADME.mdwith a test assertingcalculate_total_profitreads the realTRADE_HISTORY_FILEpath). - Green: Write the minimum implementation needed to make that test pass. Do not add unrequested functionality at this step.
- Refactor: With the test passing, clean up naming, structure, and duplication. Re-run the test after every change to confirm it still passes.
Rules that apply this workflow project-wide:
- Never present a new function, indicator, or endpoint as done without also presenting its test.
- If asked to fix a bug from the
README.mdlist, first write a test that reproduces the bug (it must fail), then fix the code until it passes. - Indicator functions (
indicators.py) must be tested against known reference values (a hand-computed or third-party-verified MACD/RSI/ADX on a fixed candle sequence), not only against "runs without throwing." - For the Phase 3 backtesting engine, tests use a fixed historical candle fixture with a known expected trade sequence and PnL — do not skip this because it's "just backtesting."
To avoid import-path and ModuleNotFoundError issues and keep module boundaries strict, all application code lives inside src/. This makes every internal import absolute (from src.trading import strategy, from src.services import binance_api) instead of relying on the working directory, and is what makes the same import paths work identically locally, in tests, and inside Docker (Phase 2).
crypto_bot_project/
legacy-experimental-phase/
wy_multicoin_engine_v47.py
wy_function_winners_list_v03.py
experiments/
VLU_test01/ # C++/Python optimization & benchmark tests
VLU_test02/ # neuro-plastic & symbolic trading experiments
VLU_test03/ # high-frequency & volatility trading models
docs/
strategies/
macd-dea-crossover.md
bollinger-lateral.md
research.md
src/
main.py # Entry point (Phase 2: FastAPI + background tasks)
api/ # Phase 2: FastAPI routers
routes.py
core/ # Shared configuration and cross-cutting concerns
config.py # pydantic-settings (.env)
exceptions.py
logger.py
trade_history.py # Total profit calculation and file persistence
db/ # Phase 2: PostgreSQL persistence
models.py # TradeHistory, SystemLog, Candle
database.py # SQLAlchemy connection / session
state/ # Phase 2: Redis shared state
redis_client.py
services/ # External integrations
binance_api.py # REST client: orders, balances, lot size
telegram.py # Notifications
websockets.py # WSS connections (klines, trades, ticker) + reconnection
market_context/ # Phase 3: regime detection and beta
regime.py
beta_calc.py
trading/ # Pure strategy and risk logic — no I/O
indicators.py # MACD, RSI, Bollinger, ADX, ATR
strategy.py # Scoring and entry/exit rules
risk.py # Stop-loss, trailing stop, position sizing
executor.py # Orchestrates buy/sell based on strategy + risk output
backtesting/ # Phase 3: historical simulation
data_loader.py
engine.py
reports.py
sentiment/ # Phase 4 (experimental): RAG ingestion and scoring
ingestion.py
rag_pipeline.py
scoring.py
agents/ # Phase 4 (experimental): advisory/gating agents
circuit_breaker.py
strategy_selector.py
optimization/ # Phase 5 (experimental): genetic algorithm search
genome.py
fitness.py
evolve.py
tests/
unit/
core/
test_trade_history.py
trading/
market_context/
sentiment/
agents/
optimization/
integration/
performance/
alembic/ # Phase 2
alembic.ini
.env.example
pyproject.toml
docker-compose.yml
Dockerfile
README.md
Before writing or modifying any file that touches the following areas, pause and confirm intent with the user:
- Any change that sets or defaults
REAL_TRADES=True, in code, configuration, tests, or examples. - Any file in
services/binance_api.pyortrading/executor.pythat changes how an order quantity or price is calculated. - Any change to
trading/risk.pythat raisesSTOP_LOSS_PERCENT, lowersTRAILING_STOP_PERCENT, or removes a minimum-lot/stepSizevalidation — these are financial safety gates. - Any change to Alembic migration files that drops a column or table (Phase 2+).
- Any change to the WebSocket reconnection logic in
services/websockets.pythat could cause the bot to silently run on stale market data. - Any change to
.envfiles, API keys, or Telegram credentials. - Any change to
agents/circuit_breaker.pyoragents/strategy_selector.pythat gives either agent a direct call path toservices/binance_api.py, or that lets a missing/failed sentiment signal be treated as a specific sentiment value instead of "unavailable" (Section 4, Phase 4 constraints). - Any change to
optimization/that writes a result directly into the live strategy configuration instead of a reviewable report. - Before running an Alembic upgrade/downgrade command (Phase 2+), confirm the current revision (
alembic current) and the target revision with the user. - Before deleting or moving a file that defines database models or API route registrations (Phase 2+), list what will be affected and ask for confirmation.
When refusing an action under this section, always state the correct alternative in the same reply — don't just decline.
| Variable | Description |
|---|---|
BINANCE_API_TEST_KEY |
Binance API key (testnet/live depending on environment) |
BINANCE_API_TEST_SECRET |
Binance API secret |
TELEGRAM_BOT_TOKEN |
Telegram bot token for notifications |
TELEGRAM_CHAT_ID |
Telegram chat ID to notify |
REAL_TRADES |
True for live orders, False for simulation (default: False) |
DATABASE_URL |
PostgreSQL connection string (Phase 2+) |
REDIS_URL |
Redis connection string (Phase 2+) |
MAX_TRADES_OPEN |
Maximum number of concurrent open positions (current default: 1) |
CAPITAL |
Total capital allocated across open positions, in USDT |
STOP_LOSS_PERCENT |
Fixed stop-loss percentage from entry (default: 0.02) |
TRAILING_STOP_PERCENT |
Trailing stop pullback percentage from the peak (default: 0.05) |
MIN_ADX |
Minimum ADX required for a valid entry signal (default: 25) |
TOP_N |
Number of top-volume symbols tracked by the Market Scanner (default: 20) |
MIN_SCORE |
Minimum score threshold for a symbol to be trade-eligible (default: 7, uncalibrated — see Phase 3) |
MAX_POSITION_USDT |
Phase 2: hard cap enforced by risk validation before any order reaches the exchange client |
SENTIMENT_API_KEYS |
Phase 4: credentials for the configured news/social/on-chain sources (one per source, not a single shared secret) |
LLM_PROVIDER |
Phase 4: active LLM provider for sentiment scoring and the strategy-selector agent (e.g. openai, gemini, groq) — changeable without touching sentiment/ or agents/ code |
CIRCUIT_BREAKER_ENABLED |
Phase 4: master switch for whether the circuit-breaker agent's halt decisions are obeyed (default: False until validated) |
GA_POPULATION_SIZE |
Phase 5: genetic algorithm population size per generation |
GA_GENERATIONS |
Phase 5: number of generations to evolve before stopping |
Every order must pass through the risk validation layer (trading/risk.py) before it reaches services/binance_api.py. Risk validation checks:
- Is there already an open, unclosed position for this symbol? (Prevents duplicate entries — see Section 4's idempotency directive.)
- Does the calculated quantity meet the exchange's minimum lot size, and is it rounded to the correct
stepSize? - Does the position size respect
CAPITAL/MAX_TRADES_OPENand any configuredMAX_POSITION_USDTcap? - Is
REAL_TRADESexplicitlyTruebefore any non-simulated order is placed?
If any check fails, the order must not be sent, the failure must be logged with the reason and the input values, and — for a real-money attempt — a Telegram notification must be sent. A rejected or ambiguous order response from the exchange is treated as "not filled," never assumed successful (see Section 4's fail-closed directive).
- Branching model: GitHub Flow (single long-lived
main, short-lived feature branches, no permanentdevelopmentbranch).mainmust always be deployable and must always default toREAL_TRADES=False. - Branch naming:
feat/<short-description>,fix/<short-description>,chore/<short-description>(e.g.,fix/round-quantity-step-size,feat/redis-shared-state). - Commit messages: Conventional Commits format —
type(scope): description(e.g.,fix(trading): round order quantity to exchange stepSize). - Workflow: branch from
main→ commit incrementally following the TDD cycle in Section 6b → open a PR tomaineven when working solo, so CI (lint, type-check, unit tests) runs before merge → squash-merge → delete the branch. - Before opening a PR:
ruff check,mypy, andpytest tests/unit/must all pass locally. - Do not commit directly to
main.