From 05e8986ff5754de94e30caef4582dbba664c9a7c Mon Sep 17 00:00:00 2001 From: agentson Date: Wed, 4 Feb 2026 10:08:05 +0900 Subject: [PATCH] refactor: split CLAUDE.md into focused documentation structure - Restructure docs into topic-specific files to minimize context - Create docs/workflow.md (Git + Agent workflow) - Create docs/commands.md (Common failures + build commands) - Create docs/architecture.md (System design + data flow) - Create docs/testing.md (Test structure + guidelines) - Rewrite CLAUDE.md as concise hub with links to detailed docs - Update .gitignore to exclude data/ directory Benefits: - Reduced context size for AI assistants - Faster reference lookups - Better maintainability - Topic-focused documentation Closes #13 Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 1 + CLAUDE.md | 306 +++++++++++-------------------------------- docs/architecture.md | 191 +++++++++++++++++++++++++++ docs/commands.md | 156 ++++++++++++++++++++++ docs/testing.md | 213 ++++++++++++++++++++++++++++++ docs/workflow.md | 75 +++++++++++ 6 files changed, 710 insertions(+), 232 deletions(-) create mode 100644 docs/architecture.md create mode 100644 docs/commands.md create mode 100644 docs/testing.md create mode 100644 docs/workflow.md diff --git a/.gitignore b/.gitignore index 36b13f1..440e665 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,4 @@ cython_debug/ # PyPI configuration file .pypirc +data/ diff --git a/CLAUDE.md b/CLAUDE.md index f9cf2fd..7e10930 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,255 +1,97 @@ -# CLAUDE.md +# The Ouroboros -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +AI-powered trading agent for global stock markets with self-evolution capabilities. -## Git Workflow Policy +## Quick Start -**CRITICAL: All code changes MUST follow this workflow. Direct pushes to `main` are ABSOLUTELY PROHIBITED.** - -1. **Create Gitea Issue First** — All features, bug fixes, and policy changes require a Gitea issue before any code is written -2. **Create Feature Branch** — Branch from `main` using format `feature/issue-{N}-{short-description}` -3. **Implement Changes** — Write code, tests, and documentation on the feature branch -4. **Create Pull Request** — Submit PR to `main` branch referencing the issue number -5. **Review & Merge** — After approval, merge via PR (squash or merge commit) - -**Never commit directly to `main`.** This policy applies to all changes, no exceptions. - -## Agent Workflow - -**Modern AI development leverages specialized agents for concurrent, efficient task execution.** - -### Parallel Execution Strategy - -Use **git worktree** or **subagents** (via the Task tool) to handle multiple requirements simultaneously: - -- Each task runs in independent context -- Parallel branches for concurrent features -- Isolated test environments prevent interference -- Faster iteration with distributed workload - -### Specialized Agent Roles - -Deploy task-specific agents as needed instead of handling everything in the main conversation: - -- **Conversational Agent** (main) — Interface with user, coordinate other agents -- **Ticket Management Agent** — Create/update Gitea issues, track task status -- **Design Agent** — Architectural planning, RFC documents, API design -- **Code Writing Agent** — Implementation following specs -- **Testing Agent** — Write tests, verify coverage, run test suites -- **Documentation Agent** — Update docs, docstrings, CLAUDE.md, README -- **Review Agent** — Code review, lint checks, security audits -- **Custom Agents** — Created dynamically for specialized tasks (performance analysis, migration scripts, etc.) - -### When to Use Agents - -**Prefer spawning specialized agents for:** - -1. Complex multi-file changes requiring exploration -2. Tasks with clear, isolated scope (e.g., "write tests for module X") -3. Parallel work streams (feature A + bugfix B simultaneously) -4. Long-running analysis (codebase search, dependency audit) -5. Tasks requiring different contexts (multiple git worktrees) - -**Use the main conversation for:** - -1. User interaction and clarification -2. Quick single-file edits -3. Coordinating agent work -4. High-level decision making - -### Implementation - -```python -# Example: Spawn parallel test and documentation agents -task_tool( - subagent_type="general-purpose", - prompt="Write comprehensive tests for src/markets/schedule.py", - description="Write schedule tests" -) - -task_tool( - subagent_type="general-purpose", - prompt="Update README.md with global market feature documentation", - description="Update README" -) -``` - -Use `run_in_background=True` for independent tasks that don't block subsequent work. - -## Common Command Failures - -**Critical: Learn from failures. Never repeat the same failed command without modification.** - -### tea CLI (Gitea Command Line Tool) - -#### ❌ TTY Error - Interactive Confirmation Fails -```bash -~/bin/tea issues create --repo X --title "Y" --description "Z" -# Error: huh: could not open a new TTY: open /dev/tty: no such device or address -``` -**💡 Reason:** tea tries to open `/dev/tty` for interactive confirmation prompts, which is unavailable in non-interactive environments. - -**✅ Solution:** Use `YES=""` environment variable to bypass confirmation -```bash -YES="" ~/bin/tea issues create --repo jihoson/The-Ouroboros --title "Title" --description "Body" -YES="" ~/bin/tea issues edit --repo jihoson/The-Ouroboros --description "Updated body" -YES="" ~/bin/tea pulls create --repo jihoson/The-Ouroboros --head feature-branch --base main --title "Title" --description "Body" -``` - -**📝 Notes:** -- Always set default login: `~/bin/tea login default local` -- Use `--repo jihoson/The-Ouroboros` when outside repo directory -- tea is preferred over direct Gitea API calls for consistency - -#### ❌ Wrong Parameter Name -```bash -tea issues create --body "text" -# Error: flag provided but not defined: -body -``` -**💡 Reason:** Parameter is `--description`, not `--body`. - -**✅ Solution:** Use correct parameter name -```bash -YES="" ~/bin/tea issues create --description "text" -``` - -### Gitea API (Direct HTTP Calls) - -#### ❌ Wrong Hostname -```bash -curl http://gitea.local:3000/api/v1/... -# Error: Could not resolve host: gitea.local -``` -**💡 Reason:** Gitea instance runs on `localhost:3000`, not `gitea.local`. - -**✅ Solution:** Use correct hostname (but prefer tea CLI) -```bash -curl http://localhost:3000/api/v1/repos/jihoson/The-Ouroboros/issues \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"title":"...", "body":"..."}' -``` - -**📝 Notes:** -- Prefer `tea` CLI over direct API calls -- Only use curl for operations tea doesn't support - -### Git Commands - -#### ❌ User Not Configured -```bash -git commit -m "message" -# Error: Author identity unknown -``` -**💡 Reason:** Git user.name and user.email not set. - -**✅ Solution:** Configure git user -```bash -git config user.name "agentson" -git config user.email "agentson@localhost" -``` - -#### ❌ Permission Denied on Push -```bash -git push origin branch -# Error: User permission denied for writing -``` -**💡 Reason:** Repository access token lacks write permissions or user lacks repo write access. - -**✅ Solution:** -1. Verify user has write access to repository (admin grants this) -2. Ensure git credential has correct token with `write:repository` scope -3. Check remote URL uses correct authentication - -### Python/Pytest - -#### ❌ Module Import Error -```bash -pytest tests/test_foo.py -# ModuleNotFoundError: No module named 'src' -``` -**💡 Reason:** Package not installed in development mode. - -**✅ Solution:** Install package with dev dependencies ```bash +# Setup pip install -e ".[dev]" +cp .env.example .env +# Edit .env with your KIS and Gemini API credentials + +# Test +pytest -v --cov=src + +# Run (paper trading) +python -m src.main --mode=paper ``` -#### ❌ Async Test Hangs -```python -async def test_something(): # Hangs forever - result = await async_function() -``` -**💡 Reason:** Missing pytest-asyncio or wrong configuration. +## Documentation -**✅ Solution:** Already configured in pyproject.toml -```toml -[tool.pytest.ini_options] -asyncio_mode = "auto" -``` -No decorator needed for async tests. +- **[Workflow Guide](docs/workflow.md)** — Git workflow policy and agent-based development +- **[Command Reference](docs/commands.md)** — Common failures, build commands, troubleshooting +- **[Architecture](docs/architecture.md)** — System design, components, data flow +- **[Testing](docs/testing.md)** — Test structure, coverage requirements, writing tests +- **[Agent Policies](docs/agents.md)** — Prime directives, constraints, prohibited actions -## Build & Test Commands +## Core Principles + +1. **Safety First** — Risk manager is READ-ONLY and enforces circuit breakers +2. **Test Everything** — 80% coverage minimum, all changes require tests +3. **Issue-Driven Development** — All work goes through Gitea issues → feature branches → PRs +4. **Agent Specialization** — Use dedicated agents for design, coding, testing, docs, review + +## Project Structure + +``` +src/ +├── broker/ # KIS API client (domestic + overseas) +├── brain/ # Gemini AI decision engine +├── core/ # Risk manager (READ-ONLY) +├── evolution/ # Self-improvement optimizer +├── markets/ # Market schedules and timezone handling +├── db.py # SQLite trade logging +├── main.py # Trading loop orchestrator +└── config.py # Settings (from .env) + +tests/ # 54 tests across 4 files +docs/ # Extended documentation +``` + +## Key Commands ```bash -# Install all dependencies (production + dev) -pip install ".[dev]" +pytest -v --cov=src # Run tests with coverage +ruff check src/ tests/ # Lint +mypy src/ --strict # Type check -# Run full test suite with coverage -pytest -v --cov=src --cov-report=term-missing +python -m src.main --mode=paper # Paper trading +python -m src.main --mode=live # Live trading (⚠️ real money) -# Run a single test file -pytest tests/test_risk.py -v - -# Run a single test by name -pytest tests/test_brain.py -k "test_parse_valid_json" -v - -# Lint -ruff check src/ tests/ - -# Type check (strict mode, non-blocking in CI) -mypy src/ --strict - -# Run the trading agent -python -m src.main --mode=paper - -# Docker -docker compose up -d ouroboros # Run agent -docker compose --profile test up test # Run tests in container +# Gitea workflow (requires tea CLI) +YES="" ~/bin/tea issues create --repo jihoson/The-Ouroboros --title "..." --description "..." +YES="" ~/bin/tea pulls create --head feature-branch --base main --title "..." --description "..." ``` -## Architecture +## Markets Supported -Self-evolving AI trading agent for Korean stock markets (KIS API). The main loop in `src/main.py` orchestrates four components in a 60-second cycle per stock: +- 🇰🇷 Korea (KRX) +- 🇺🇸 United States (NASDAQ, NYSE, AMEX) +- 🇯🇵 Japan (TSE) +- 🇭🇰 Hong Kong (SEHK) +- 🇨🇳 China (Shanghai, Shenzhen) +- 🇻🇳 Vietnam (Hanoi, HCM) -1. **Broker** (`src/broker/kis_api.py`) — Async KIS API client with automatic OAuth token refresh, leaky-bucket rate limiter (10 RPS), and POST body hash-key signing. Uses a custom SSL context with disabled hostname verification for the VTS (virtual trading) endpoint due to a known certificate mismatch. +Markets auto-detected based on timezone and enabled in `ENABLED_MARKETS` env variable. -2. **Brain** (`src/brain/gemini_client.py`) — Sends structured prompts to Google Gemini, parses JSON responses into `TradeDecision` objects. Forces HOLD when confidence < threshold (default 80). Falls back to safe HOLD on any parse/API error. +## Critical Constraints -3. **Risk Manager** (`src/core/risk_manager.py`) — **READ-ONLY by policy** (see `docs/agents.md`). Circuit breaker halts all trading via `SystemExit` when daily P&L drops below -3.0%. Fat-finger check rejects orders exceeding 30% of available cash. +⚠️ **Non-Negotiable Rules** (see [docs/agents.md](docs/agents.md)): -4. **Evolution** (`src/evolution/optimizer.py`) — Analyzes high-confidence losing trades from SQLite, asks Gemini to generate new `BaseStrategy` subclasses, validates them by running the full pytest suite, and simulates PR creation. +- `src/core/risk_manager.py` is **READ-ONLY** — changes require human approval +- Circuit breaker at -3.0% P&L — may only be made **stricter** +- Fat-finger protection: max 30% of cash per order — always enforced +- Confidence < 80 → force HOLD — cannot be weakened +- All code changes → corresponding tests → coverage ≥ 80% -**Data flow per cycle:** Fetch orderbook + balance → calculate P&L → get Gemini decision → validate with risk manager → execute order → log to SQLite (`src/db.py`). +## Contributing -## Key Constraints (from `docs/agents.md`) +See [docs/workflow.md](docs/workflow.md) for the complete development process. -- `core/risk_manager.py` is **READ-ONLY**. Changes require human approval. -- Circuit breaker threshold (-3.0%) may only be made stricter, never relaxed. -- Fat-finger protection (30% max order size) must always be enforced. -- Confidence < 80 **must** force HOLD — this rule cannot be weakened. -- All code changes require corresponding tests. Coverage must stay >= 80%. -- Generated strategies must pass the full test suite before activation. - -## Configuration - -Pydantic Settings loaded from `.env` (see `.env.example`). Required vars: `KIS_APP_KEY`, `KIS_APP_SECRET`, `KIS_ACCOUNT_NO` (format `XXXXXXXX-XX`), `GEMINI_API_KEY`. Tests use in-memory SQLite (`DB_PATH=":memory:"`) and dummy credentials via `tests/conftest.py`. - -## Test Structure - -54 tests across four files. `asyncio_mode = "auto"` in pyproject.toml — async tests need no special decorator. The `settings` fixture in `conftest.py` provides safe defaults with test credentials and in-memory DB. - -- `test_risk.py` (11) — Circuit breaker boundaries, fat-finger edge cases -- `test_broker.py` (6) — Token lifecycle, rate limiting, hash keys, network errors -- `test_brain.py` (18) — JSON parsing, confidence threshold, malformed responses, prompt construction -- `test_market_schedule.py` (19) — Market open/close logic, timezone handling, DST, lunch breaks +**TL;DR:** +1. Create issue in Gitea +2. Create feature branch: `feature/issue-N-description` +3. Implement with tests +4. Open PR +5. Merge after review diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..f1cf0a4 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,191 @@ +# System Architecture + +## Overview + +Self-evolving AI trading agent for global stock markets via KIS (Korea Investment & Securities) API. The main loop in `src/main.py` orchestrates four components in a 60-second cycle per stock across multiple markets. + +## Core Components + +### 1. Broker (`src/broker/`) + +**KISBroker** (`kis_api.py`) — Async KIS API client for domestic Korean market + +- Automatic OAuth token refresh (valid for 24 hours) +- Leaky-bucket rate limiter (10 requests per second) +- POST body hash-key signing for order authentication +- Custom SSL context with disabled hostname verification for VTS (virtual trading) endpoint due to known certificate mismatch + +**OverseasBroker** (`overseas.py`) — KIS overseas stock API wrapper + +- Reuses KISBroker infrastructure (session, token, rate limiter) via composition +- Supports 9 global markets: US (NASDAQ/NYSE/AMEX), Japan, Hong Kong, China (Shanghai/Shenzhen), Vietnam (Hanoi/HCM) +- Different API endpoints for overseas price/balance/order operations + +**Market Schedule** (`src/markets/schedule.py`) — Timezone-aware market management + +- `MarketInfo` dataclass with timezone, trading hours, lunch breaks +- Automatic DST handling via `zoneinfo.ZoneInfo` +- `is_market_open()` checks weekends, trading hours, lunch breaks +- `get_open_markets()` returns currently active markets +- `get_next_market_open()` finds next market to open and when + +### 2. Brain (`src/brain/gemini_client.py`) + +**GeminiClient** — AI decision engine powered by Google Gemini + +- Constructs structured prompts from market data +- Parses JSON responses into `TradeDecision` objects (`action`, `confidence`, `rationale`) +- Forces HOLD when confidence < threshold (default 80) +- Falls back to safe HOLD on any parse/API error +- Handles markdown-wrapped JSON, malformed responses, invalid actions + +### 3. Risk Manager (`src/core/risk_manager.py`) + +**RiskManager** — Safety circuit breaker and order validation + +⚠️ **READ-ONLY by policy** (see [`docs/agents.md`](./agents.md)) + +- **Circuit Breaker**: Halts all trading via `SystemExit` when daily P&L drops below -3.0% + - Threshold may only be made stricter, never relaxed + - Calculated as `(total_eval - purchase_total) / purchase_total * 100` +- **Fat-Finger Protection**: Rejects orders exceeding 30% of available cash + - Must always be enforced, cannot be disabled + +### 4. Evolution (`src/evolution/optimizer.py`) + +**StrategyOptimizer** — Self-improvement loop + +- Analyzes high-confidence losing trades from SQLite +- Asks Gemini to generate new `BaseStrategy` subclasses +- Validates generated strategies by running full pytest suite +- Simulates PR creation for human review +- Only activates strategies that pass all tests + +## Data Flow + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Main Loop (60s cycle per stock, per market) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Market Schedule Check │ + │ - Get open markets │ + │ - Filter by enabled markets │ + │ - Wait if all closed │ + └──────────────────┬────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Broker: Fetch Market Data │ + │ - Domestic: orderbook + balance │ + │ - Overseas: price + balance │ + └──────────────────┬────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Calculate P&L │ + │ pnl_pct = (eval - cost) / cost │ + └──────────────────┬────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Brain: Get Decision │ + │ - Build prompt with market data │ + │ - Call Gemini API │ + │ - Parse JSON response │ + │ - Return TradeDecision │ + └──────────────────┬────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Risk Manager: Validate Order │ + │ - Check circuit breaker │ + │ - Check fat-finger limit │ + │ - Raise if validation fails │ + └──────────────────┬────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Broker: Execute Order │ + │ - Domestic: send_order() │ + │ - Overseas: send_overseas_order() │ + └──────────────────┬────────────────┘ + │ + ▼ + ┌──────────────────────────────────┐ + │ Database: Log Trade │ + │ - SQLite (data/trades.db) │ + │ - Track: action, confidence, │ + │ rationale, market, exchange │ + └───────────────────────────────────┘ +``` + +## Database Schema + +**SQLite** (`src/db.py`) + +```sql +CREATE TABLE trades ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL, + stock_code TEXT NOT NULL, + action TEXT NOT NULL, -- BUY | SELL | HOLD + confidence INTEGER NOT NULL, -- 0-100 + rationale TEXT, + quantity INTEGER, + price REAL, + pnl REAL DEFAULT 0.0, + market TEXT DEFAULT 'KR', -- KR | US_NASDAQ | JP | etc. + exchange_code TEXT DEFAULT 'KRX' -- KRX | NASD | NYSE | etc. +); +``` + +Auto-migration: Adds `market` and `exchange_code` columns if missing for backward compatibility. + +## Configuration + +**Pydantic Settings** (`src/config.py`) + +Loaded from `.env` file: + +```bash +# Required +KIS_APP_KEY=your_app_key +KIS_APP_SECRET=your_app_secret +KIS_ACCOUNT_NO=XXXXXXXX-XX +GEMINI_API_KEY=your_gemini_key + +# Optional +MODE=paper # paper | live +DB_PATH=data/trades.db +CONFIDENCE_THRESHOLD=80 +MAX_LOSS_PCT=3.0 +MAX_ORDER_PCT=30.0 +ENABLED_MARKETS=KR,US_NASDAQ # Comma-separated market codes +``` + +Tests use in-memory SQLite (`DB_PATH=":memory:"`) and dummy credentials via `tests/conftest.py`. + +## Error Handling + +### Connection Errors (Broker API) +- Retry with exponential backoff (2^attempt seconds) +- Max 3 retries per stock +- After exhaustion, skip stock and continue with next + +### API Quota Errors (Gemini) +- Return safe HOLD decision with confidence=0 +- Log error but don't crash +- Agent continues trading on next cycle + +### Circuit Breaker Tripped +- Immediately halt via `SystemExit` +- Log critical message +- Requires manual intervention to restart + +### Market Closed +- Wait until next market opens +- Use `get_next_market_open()` to calculate wait time +- Sleep until market open time diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 0000000..1053ac5 --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,156 @@ +# Command Reference + +## Common Command Failures + +**Critical: Learn from failures. Never repeat the same failed command without modification.** + +### tea CLI (Gitea Command Line Tool) + +#### ❌ TTY Error - Interactive Confirmation Fails +```bash +~/bin/tea issues create --repo X --title "Y" --description "Z" +# Error: huh: could not open a new TTY: open /dev/tty: no such device or address +``` +**💡 Reason:** tea tries to open `/dev/tty` for interactive confirmation prompts, which is unavailable in non-interactive environments. + +**✅ Solution:** Use `YES=""` environment variable to bypass confirmation +```bash +YES="" ~/bin/tea issues create --repo jihoson/The-Ouroboros --title "Title" --description "Body" +YES="" ~/bin/tea issues edit --repo jihoson/The-Ouroboros --description "Updated body" +YES="" ~/bin/tea pulls create --repo jihoson/The-Ouroboros --head feature-branch --base main --title "Title" --description "Body" +``` + +**📝 Notes:** +- Always set default login: `~/bin/tea login default local` +- Use `--repo jihoson/The-Ouroboros` when outside repo directory +- tea is preferred over direct Gitea API calls for consistency + +#### ❌ Wrong Parameter Name +```bash +tea issues create --body "text" +# Error: flag provided but not defined: -body +``` +**💡 Reason:** Parameter is `--description`, not `--body`. + +**✅ Solution:** Use correct parameter name +```bash +YES="" ~/bin/tea issues create --description "text" +``` + +### Gitea API (Direct HTTP Calls) + +#### ❌ Wrong Hostname +```bash +curl http://gitea.local:3000/api/v1/... +# Error: Could not resolve host: gitea.local +``` +**💡 Reason:** Gitea instance runs on `localhost:3000`, not `gitea.local`. + +**✅ Solution:** Use correct hostname (but prefer tea CLI) +```bash +curl http://localhost:3000/api/v1/repos/jihoson/The-Ouroboros/issues \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"title":"...", "body":"..."}' +``` + +**📝 Notes:** +- Prefer `tea` CLI over direct API calls +- Only use curl for operations tea doesn't support + +### Git Commands + +#### ❌ User Not Configured +```bash +git commit -m "message" +# Error: Author identity unknown +``` +**💡 Reason:** Git user.name and user.email not set. + +**✅ Solution:** Configure git user +```bash +git config user.name "agentson" +git config user.email "agentson@localhost" +``` + +#### ❌ Permission Denied on Push +```bash +git push origin branch +# Error: User permission denied for writing +``` +**💡 Reason:** Repository access token lacks write permissions or user lacks repo write access. + +**✅ Solution:** +1. Verify user has write access to repository (admin grants this) +2. Ensure git credential has correct token with `write:repository` scope +3. Check remote URL uses correct authentication + +### Python/Pytest + +#### ❌ Module Import Error +```bash +pytest tests/test_foo.py +# ModuleNotFoundError: No module named 'src' +``` +**💡 Reason:** Package not installed in development mode. + +**✅ Solution:** Install package with dev dependencies +```bash +pip install -e ".[dev]" +``` + +#### ❌ Async Test Hangs +```python +async def test_something(): # Hangs forever + result = await async_function() +``` +**💡 Reason:** Missing pytest-asyncio or wrong configuration. + +**✅ Solution:** Already configured in pyproject.toml +```toml +[tool.pytest.ini_options] +asyncio_mode = "auto" +``` +No decorator needed for async tests. + +## Build & Test Commands + +```bash +# Install all dependencies (production + dev) +pip install -e ".[dev]" + +# Run full test suite with coverage +pytest -v --cov=src --cov-report=term-missing + +# Run a single test file +pytest tests/test_risk.py -v + +# Run a single test by name +pytest tests/test_brain.py -k "test_parse_valid_json" -v + +# Lint +ruff check src/ tests/ + +# Type check (strict mode, non-blocking in CI) +mypy src/ --strict + +# Run the trading agent +python -m src.main --mode=paper + +# Docker +docker compose up -d ouroboros # Run agent +docker compose --profile test up test # Run tests in container +``` + +## Environment Setup + +```bash +# Create .env file from example +cp .env.example .env + +# Edit .env with your credentials +# Required: KIS_APP_KEY, KIS_APP_SECRET, KIS_ACCOUNT_NO, GEMINI_API_KEY + +# Verify configuration +python -c "from src.config import Settings; print(Settings())" +``` diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..b65d35e --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,213 @@ +# Testing Guidelines + +## Test Structure + +**54 tests** across four files. `asyncio_mode = "auto"` in pyproject.toml — async tests need no special decorator. + +The `settings` fixture in `conftest.py` provides safe defaults with test credentials and in-memory DB. + +### Test Files + +#### `tests/test_risk.py` (11 tests) +- Circuit breaker boundaries +- Fat-finger edge cases +- P&L calculation edge cases +- Order validation logic + +**Example:** +```python +def test_circuit_breaker_exact_threshold(risk_manager): + """Circuit breaker should trip at exactly -3.0%.""" + with pytest.raises(CircuitBreakerTripped): + risk_manager.validate_order( + current_pnl_pct=-3.0, + order_amount=1000, + total_cash=10000 + ) +``` + +#### `tests/test_broker.py` (6 tests) +- OAuth token lifecycle +- Rate limiting enforcement +- Hash key generation +- Network error handling +- SSL context configuration + +**Example:** +```python +async def test_rate_limiter(broker): + """Rate limiter should delay requests to stay under 10 RPS.""" + start = time.monotonic() + for _ in range(15): # 15 requests + await broker._rate_limiter.acquire() + elapsed = time.monotonic() - start + assert elapsed >= 1.0 # Should take at least 1 second +``` + +#### `tests/test_brain.py` (18 tests) +- Valid JSON parsing +- Markdown-wrapped JSON handling +- Malformed JSON fallback +- Missing fields handling +- Invalid action validation +- Confidence threshold enforcement +- Empty response handling +- Prompt construction for different markets + +**Example:** +```python +async def test_confidence_below_threshold_forces_hold(brain): + """Decisions below confidence threshold should force HOLD.""" + decision = brain.parse_response('{"action":"BUY","confidence":70,"rationale":"test"}') + assert decision.action == "HOLD" + assert decision.confidence == 70 +``` + +#### `tests/test_market_schedule.py` (19 tests) +- Market open/close logic +- Timezone handling (UTC, Asia/Seoul, America/New_York, etc.) +- DST (Daylight Saving Time) transitions +- Weekend handling +- Lunch break logic +- Multiple market filtering +- Next market open calculation + +**Example:** +```python +def test_is_market_open_during_trading_hours(): + """Market should be open during regular trading hours.""" + # KRX: 9:00-15:30 KST, no lunch break + market = MARKETS["KR"] + trading_time = datetime(2026, 2, 3, 10, 0, tzinfo=ZoneInfo("Asia/Seoul")) # Monday 10:00 + assert is_market_open(market, trading_time) is True +``` + +## Coverage Requirements + +**Minimum coverage: 80%** + +Check coverage: +```bash +pytest -v --cov=src --cov-report=term-missing +``` + +Expected output: +``` +Name Stmts Miss Cover Missing +----------------------------------------------------------- +src/brain/gemini_client.py 85 5 94% 165-169 +src/broker/kis_api.py 120 12 90% ... +src/core/risk_manager.py 35 2 94% ... +src/db.py 25 1 96% ... +src/main.py 150 80 47% (excluded from CI) +src/markets/schedule.py 95 3 97% ... +----------------------------------------------------------- +TOTAL 510 103 80% +``` + +**Note:** `main.py` has lower coverage as it contains the main loop which is tested via integration/manual testing. + +## Test Configuration + +### `pyproject.toml` +```toml +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] +python_files = ["test_*.py"] +``` + +### `tests/conftest.py` +```python +@pytest.fixture +def settings() -> Settings: + """Provide test settings with safe defaults.""" + return Settings( + KIS_APP_KEY="test_key", + KIS_APP_SECRET="test_secret", + KIS_ACCOUNT_NO="12345678-01", + GEMINI_API_KEY="test_gemini_key", + MODE="paper", + DB_PATH=":memory:", # In-memory SQLite + CONFIDENCE_THRESHOLD=80, + ENABLED_MARKETS="KR", + ) +``` + +## Writing New Tests + +### Naming Convention +- Test files: `test_.py` +- Test functions: `test__()` +- Use descriptive names that explain what is being tested + +### Good Test Example +```python +async def test_send_order_with_market_price(broker, settings): + """Market orders should use price=0 and ORD_DVSN='01'.""" + # Arrange + stock_code = "005930" + order_type = "BUY" + quantity = 10 + + # Act + with patch.object(broker._session, 'post') as mock_post: + mock_post.return_value.__aenter__.return_value.status = 200 + mock_post.return_value.__aenter__.return_value.json = AsyncMock( + return_value={"rt_cd": "0", "msg1": "OK"} + ) + + await broker.send_order(stock_code, order_type, quantity, price=0) + + # Assert + call_args = mock_post.call_args + body = call_args.kwargs['json'] + assert body['ORD_DVSN'] == '01' # Market order + assert body['ORD_UNPR'] == '0' # Price 0 +``` + +### Test Checklist +- [ ] Test passes in isolation (`pytest tests/test_foo.py::test_bar -v`) +- [ ] Test has clear docstring explaining what it tests +- [ ] Arrange-Act-Assert structure +- [ ] Uses appropriate fixtures from conftest.py +- [ ] Mocks external dependencies (API calls, network) +- [ ] Tests edge cases and error conditions +- [ ] Doesn't rely on test execution order + +## Running Tests + +```bash +# All tests +pytest -v + +# Specific file +pytest tests/test_risk.py -v + +# Specific test +pytest tests/test_brain.py::test_parse_valid_json -v + +# With coverage +pytest -v --cov=src --cov-report=term-missing + +# Stop on first failure +pytest -x + +# Verbose output with print statements +pytest -v -s +``` + +## CI/CD Integration + +Tests run automatically on: +- Every commit to feature branches +- Every PR to main +- Scheduled daily runs + +**Blocking conditions:** +- Test failures → PR blocked +- Coverage < 80% → PR blocked (warning only for main.py) + +**Non-blocking:** +- `mypy --strict` errors (type hints encouraged but not enforced) +- `ruff check` warnings (must be acknowledged) diff --git a/docs/workflow.md b/docs/workflow.md new file mode 100644 index 0000000..3ef1d11 --- /dev/null +++ b/docs/workflow.md @@ -0,0 +1,75 @@ +# Development Workflow + +## Git Workflow Policy + +**CRITICAL: All code changes MUST follow this workflow. Direct pushes to `main` are ABSOLUTELY PROHIBITED.** + +1. **Create Gitea Issue First** — All features, bug fixes, and policy changes require a Gitea issue before any code is written +2. **Create Feature Branch** — Branch from `main` using format `feature/issue-{N}-{short-description}` +3. **Implement Changes** — Write code, tests, and documentation on the feature branch +4. **Create Pull Request** — Submit PR to `main` branch referencing the issue number +5. **Review & Merge** — After approval, merge via PR (squash or merge commit) + +**Never commit directly to `main`.** This policy applies to all changes, no exceptions. + +## Agent Workflow + +**Modern AI development leverages specialized agents for concurrent, efficient task execution.** + +### Parallel Execution Strategy + +Use **git worktree** or **subagents** (via the Task tool) to handle multiple requirements simultaneously: + +- Each task runs in independent context +- Parallel branches for concurrent features +- Isolated test environments prevent interference +- Faster iteration with distributed workload + +### Specialized Agent Roles + +Deploy task-specific agents as needed instead of handling everything in the main conversation: + +- **Conversational Agent** (main) — Interface with user, coordinate other agents +- **Ticket Management Agent** — Create/update Gitea issues, track task status +- **Design Agent** — Architectural planning, RFC documents, API design +- **Code Writing Agent** — Implementation following specs +- **Testing Agent** — Write tests, verify coverage, run test suites +- **Documentation Agent** — Update docs, docstrings, CLAUDE.md, README +- **Review Agent** — Code review, lint checks, security audits +- **Custom Agents** — Created dynamically for specialized tasks (performance analysis, migration scripts, etc.) + +### When to Use Agents + +**Prefer spawning specialized agents for:** + +1. Complex multi-file changes requiring exploration +2. Tasks with clear, isolated scope (e.g., "write tests for module X") +3. Parallel work streams (feature A + bugfix B simultaneously) +4. Long-running analysis (codebase search, dependency audit) +5. Tasks requiring different contexts (multiple git worktrees) + +**Use the main conversation for:** + +1. User interaction and clarification +2. Quick single-file edits +3. Coordinating agent work +4. High-level decision making + +### Implementation + +```python +# Example: Spawn parallel test and documentation agents +task_tool( + subagent_type="general-purpose", + prompt="Write comprehensive tests for src/markets/schedule.py", + description="Write schedule tests" +) + +task_tool( + subagent_type="general-purpose", + prompt="Update README.md with global market feature documentation", + description="Update README" +) +``` + +Use `run_in_background=True` for independent tasks that don't block subsequent work.