trading/.planning/codebase/CONVENTIONS.md
2026-02-23 20:04:05 +00:00

11 KiB

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:
    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:
    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:

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
    """Thin wrappers around redis-py Streams for publish/consume with JSON serialization."""
    
  • Class docstrings: Describe the class purpose and key behavior
    class StreamPublisher:
        """Publishes JSON-encoded messages to a Redis Stream."""
    
  • Method docstrings: Use Google/NumPy style for async methods
    async def ensure_group(self) -> None:
        """Create the consumer group if it does not already exist."""
    
  • Parameters/Returns documented in docstrings for public APIs:
    """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):
    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:
    __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