276 lines
11 KiB
Markdown
276 lines
11 KiB
Markdown
# Coding Conventions
|
|
|
|
**Analysis Date:** 2026-02-23
|
|
|
|
## Naming Patterns
|
|
|
|
**Files (Python):**
|
|
- Modules: lowercase with underscores (e.g., `redis_streams.py`, `finbert_analyzer.py`)
|
|
- Packages: underscores, not hyphens (e.g., `news_fetcher`, `sentiment_analyzer`)
|
|
- Private modules: prefix with underscore (e.g., `_deduplicate_and_publish`)
|
|
|
|
**Files (TypeScript/React):**
|
|
- Components: PascalCase (e.g., `MetricsRow.tsx`, `ProtectedRoute.tsx`)
|
|
- Hooks: camelCase with `use` prefix (e.g., `useAuth.ts`, `useWebSocket.ts`)
|
|
- Utilities/APIs: camelCase (e.g., `client.ts`, `auth.ts`)
|
|
- Config files: camelCase with dots (e.g., `tsconfig.app.json`, `vite.config.ts`)
|
|
|
|
**Functions and Methods:**
|
|
- Snake_case in Python (e.g., `_deduplicate_and_publish`, `ensure_group`)
|
|
- CamelCase in TypeScript/React (e.g., `startRegistration`, `useCallback`)
|
|
- Async functions: prefix with `async` keyword, no special naming convention
|
|
- Private/internal functions: prefix with underscore in Python (e.g., `_make_config`, `_make_signal`)
|
|
|
|
**Variables:**
|
|
- Snake_case in Python for all variables (e.g., `session_factory`, `confidence_threshold`)
|
|
- CamelCase in TypeScript (e.g., `isAuthenticated`, `loading`, `token`)
|
|
- Constants: UPPER_CASE with underscores (e.g., `SEEN_HASHES_KEY = "news:seen_hashes"`, `RAW_STREAM = "test:news:raw"`)
|
|
- Loop variables and temporaries: follow standard conventions (e.g., `msg_id`, `score`, `ticker`)
|
|
|
|
**Types and Classes:**
|
|
- Classes: PascalCase (e.g., `StreamPublisher`, `TradeEvaluator`, `BaseStrategy`)
|
|
- Enums: PascalCase (e.g., `TradeSide`, `TradeStatus`, `SignalDirection`)
|
|
- Type hints: use full import path or import explicitly (e.g., `list[PositionInfo]`, `dict[str, str]`)
|
|
- Generic types: use Python 3.12+ syntax without `typing.` prefix (e.g., `dict`, `list`, `tuple`)
|
|
|
|
**Interfaces (TypeScript):**
|
|
- PascalCase with `Props` suffix for component props (e.g., `MetricsRowProps`)
|
|
- PascalCase for return types (e.g., `UseAuthReturn`)
|
|
- Prefix common interfaces: `I` is not used; use descriptive names instead
|
|
|
|
## Code Style
|
|
|
|
**Formatting (Python):**
|
|
- Line length: 120 characters (configured in `pyproject.toml` under `[tool.ruff]`)
|
|
- Target Python version: 3.12 (configured under `[tool.ruff] target-version`)
|
|
- Formatter: Ruff (for linting and formatting)
|
|
- No Black configuration — Ruff is the primary tool
|
|
|
|
**Formatting (TypeScript/React):**
|
|
- Target: ES2022 (configured in `tsconfig.app.json`)
|
|
- Module resolution: bundler
|
|
- Strict mode: enabled (`"strict": true`)
|
|
- JSX mode: react-jsx
|
|
|
|
**Linting (Python):**
|
|
- Tool: Ruff (`ruff>=0.3`)
|
|
- Key rules: Follow Python 3.12 conventions, enforce async functions
|
|
- MyPy: enabled (`mypy>=1.8`)
|
|
- `warn_return_any = true`
|
|
- `warn_unused_configs = true`
|
|
|
|
**Linting (TypeScript):**
|
|
- ESLint with flat config (`eslint.config.js` in `dashboard/`)
|
|
- Extends:
|
|
- `@eslint/js` (recommended)
|
|
- `typescript-eslint` (recommended)
|
|
- `react-hooks` (react-hooks/exhaustive-deps)
|
|
- `react-refresh` (for Vite)
|
|
- Global ignores: `dist/`
|
|
- Files: `**/*.{ts,tsx}`
|
|
|
|
## Import Organization
|
|
|
|
**Python Import Order:**
|
|
1. `from __future__ import annotations` (always first if present)
|
|
2. Standard library: `import asyncio`, `import json`, `import logging`, etc.
|
|
3. Third-party packages: `from redis.asyncio import Redis`, `from pydantic import BaseModel`, etc.
|
|
4. Shared modules: `from shared.config import BaseConfig`, `from shared.redis_streams import StreamPublisher`
|
|
5. Service-specific modules: `from services.news_fetcher.config import NewsFetcherConfig`
|
|
6. Absolute imports preferred; relative imports acceptable within a service
|
|
|
|
**TypeScript Import Order:**
|
|
1. React and core libraries: `import { useState, useCallback } from 'react'`
|
|
2. Routing: `import { Routes, Route } from 'react-router-dom'`
|
|
3. Third-party UI/packages: `import { startRegistration } from '@simplewebauthn/browser'`
|
|
4. Internal utilities/API: `import { client } from '../api/client'`
|
|
5. Components: `import { MetricsRow } from '../components/MetricsRow'`
|
|
6. Hooks: `import { useAuth } from '../hooks/useAuth'`
|
|
|
|
**Path Aliases:**
|
|
- Python: No path aliases configured; use absolute imports from project root
|
|
- TypeScript: None configured in `tsconfig.app.json`; use relative paths with `../`
|
|
|
|
## Error Handling
|
|
|
|
**Python Error Handling:**
|
|
- Use `try/except` for external service calls (Redis, database, API calls)
|
|
- Log all exceptions at the point of handling: `logger.exception("Context: %s", variable)`
|
|
- Specific exception handling before generic: `except IntegrityError:` before `except Exception:`
|
|
- Re-raise specific errors or return default/None on fallback paths
|
|
- Example from `sentiment_analyzer/main.py`:
|
|
```python
|
|
try:
|
|
async with db_session_factory() as session:
|
|
# persist to DB
|
|
except IntegrityError:
|
|
# Handle duplicate
|
|
pass
|
|
except Exception:
|
|
logger.exception("Error processing article: %s", data.get("title", "<unknown>"))
|
|
```
|
|
- Async functions use `try/except` around `await` calls
|
|
- Use `asyncio.TimeoutError` for timeout handling
|
|
|
|
**TypeScript/React Error Handling:**
|
|
- Catch errors from API calls and set error state: `err?.response?.data?.detail || err?.message`
|
|
- Always provide fallback messages: `'Registration failed'` as default
|
|
- Log error context: `throw err` after setting error state to propagate to caller if needed
|
|
- Example from `useAuth.ts`:
|
|
```typescript
|
|
catch (err: any) {
|
|
const message = err?.response?.data?.detail || err?.message || 'Registration failed';
|
|
setError(message);
|
|
throw err;
|
|
}
|
|
```
|
|
|
|
## Logging
|
|
|
|
**Framework:** Python uses `logging` module; no structured logging library
|
|
|
|
**Patterns:**
|
|
- Create logger at module level: `logger = logging.getLogger(__name__)`
|
|
- Use appropriate levels:
|
|
- `logger.debug()` — Detailed debug info (e.g., "Published to stream:xxx")
|
|
- `logger.info()` — General informational (e.g., "Created consumer group…")
|
|
- `logger.warning()` — Warning conditions (e.g., "Order rejected by broker")
|
|
- `logger.exception()` — Log exceptions with stack trace (always use at exception point)
|
|
- Always include context in log messages using `%` formatting: `logger.info("Message with %s", variable)`
|
|
- Do NOT use f-strings in logging (allows lazy evaluation)
|
|
- Log level configuration: `log_level` in `BaseConfig` (default: "INFO")
|
|
|
|
**Examples from codebase:**
|
|
```python
|
|
logger.debug("Published to %s: %s", self.stream, msg_id)
|
|
logger.info("Created consumer group %s on %s", self.group, self.stream)
|
|
logger.exception("Ollama analysis failed") # Includes stack trace
|
|
logger.warning("Order rejected by Alpaca: %s", exc)
|
|
```
|
|
|
|
## Comments
|
|
|
|
**When to Comment:**
|
|
- Complex business logic that isn't self-documenting (e.g., weight adjustment formulas, P&L calculations)
|
|
- Non-obvious design decisions (e.g., "SADD returns 1 if the member was added (i.e. not already present)")
|
|
- Caveats or workarounds (e.g., "BUSYGROUP means group already exists — expected on subsequent starts.")
|
|
- Public APIs and exported functions should have docstrings
|
|
|
|
**When NOT to Comment:**
|
|
- Self-explanatory code (variable names, clear function logic)
|
|
- Redundant comments that repeat the code
|
|
- Outdated comments (remove or update)
|
|
|
|
**Docstring/Comments:**
|
|
- Module docstrings: Present at top of file, describe purpose
|
|
```python
|
|
"""Thin wrappers around redis-py Streams for publish/consume with JSON serialization."""
|
|
```
|
|
- Class docstrings: Describe the class purpose and key behavior
|
|
```python
|
|
class StreamPublisher:
|
|
"""Publishes JSON-encoded messages to a Redis Stream."""
|
|
```
|
|
- Method docstrings: Use Google/NumPy style for async methods
|
|
```python
|
|
async def ensure_group(self) -> None:
|
|
"""Create the consumer group if it does not already exist."""
|
|
```
|
|
- Parameters/Returns documented in docstrings for public APIs:
|
|
```python
|
|
"""Score a single article and publish one ScoredArticle per extracted ticker.
|
|
|
|
Parameters
|
|
----------
|
|
article:
|
|
The raw article consumed from the ``news:raw`` stream.
|
|
"""
|
|
```
|
|
|
|
**Inline Comments:**
|
|
- Prefix with `#` and space: `# This explains why…`
|
|
- Place above the code line (not at end of line unless very brief)
|
|
- Example: `# SADD returns 1 if the member was added (i.e. not already present)`
|
|
|
|
## Function Design
|
|
|
|
**Size Guidelines:**
|
|
- Functions should be focused and single-responsibility
|
|
- Async functions in services are typically 5-30 lines (after docstring)
|
|
- Helper functions (prefixed with `_`) are 3-20 lines
|
|
- Example: `_deduplicate_and_publish` is ~20 lines, handles one concern
|
|
|
|
**Parameters:**
|
|
- Use keyword arguments for clarity in calls: `await publish(data=article, index=stream)`
|
|
- Type hints are required for all parameters and returns
|
|
- Default parameters allowed (e.g., `batch_size: int = 10`)
|
|
- For configuration, pass config objects rather than many flags: `config: MyConfig` not `threshold=0.5, timeout=10`
|
|
|
|
**Return Values:**
|
|
- Use union types for optional/multiple returns: `TradeSignal | None` (Python 3.10+ syntax)
|
|
- Return early for error cases (guard clauses):
|
|
```python
|
|
if not tickers:
|
|
logger.debug("No tickers found")
|
|
return
|
|
```
|
|
- For async functions, return types should use `Awaitable` or direct annotation: `async def x() -> PositionInfo:`
|
|
|
|
## Module Design
|
|
|
|
**Exports:**
|
|
- Use `__all__` in modules that re-export from submodules
|
|
- Example from `shared/models/__init__.py`:
|
|
```python
|
|
__all__ = [
|
|
"Base",
|
|
"TimestampMixin",
|
|
# Trading
|
|
"Strategy",
|
|
"Signal",
|
|
# ...
|
|
]
|
|
```
|
|
- Imports are grouped by category (Trading, News, Learning, Auth, Timeseries) with comments
|
|
|
|
**Barrel Files:**
|
|
- `shared/models/__init__.py` acts as a barrel, importing all models so Alembic can discover them
|
|
- Comment each import group for clarity
|
|
- Do NOT use wildcard imports (`from x import *`)
|
|
|
|
**Config Classes:**
|
|
- All config classes extend `shared.config.BaseConfig` (Pydantic BaseSettings)
|
|
- Use `model_config = {"env_prefix": "TRADING_"}` for environment variable loading
|
|
- Example: `services/news_fetcher/config.py` extends `BaseConfig`
|
|
|
|
**Service Entry Points:**
|
|
- Located at `services/{service_name}/main.py`
|
|
- Contains `async def main()` or async generator functions
|
|
- Modules imported from shared libraries and service-specific modules
|
|
- Signal handling for graceful shutdown
|
|
|
|
## Special Conventions
|
|
|
|
**Redis Stream Constants:**
|
|
- Define at module level as uppercase strings
|
|
- Example: `NEWS_RAW_STREAM = "news:raw"`, `SEEN_HASHES_KEY = "news:seen_hashes"`
|
|
|
|
**Pydantic Models:**
|
|
- Use `model_dump(mode="json")` when serializing to JSON (v2 syntax)
|
|
- Set `model_config = {"from_attributes": True}` for ORM mapping
|
|
- Enum fields use value comparison: `TradeSide.BUY == "BUY"`
|
|
|
|
**SQLAlchemy (2.0+):**
|
|
- Use async sessions: `async_sessionmaker` with async context managers
|
|
- Use `mapped_column` style (not `Column`)
|
|
- Type hints required on model fields
|
|
- Example: models in `shared/models/trading.py`
|
|
|
|
**Test Naming:**
|
|
- Test functions: `test_{component}_{scenario}` (e.g., `test_publisher_publishes_json`)
|
|
- Test classes: `Test{ComponentName}` (e.g., `TestEvaluateProfitableTrade`)
|
|
- Helper functions in tests: prefix with `_make_` (e.g., `_make_config`, `_make_signal`)
|
|
|
|
---
|
|
|
|
*Convention analysis: 2026-02-23*
|