# 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", "")) ``` - 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*