Skip to content

Code Quality Standards

This document describes the code quality standards and tools used in the DSTA project.

Overview

DSTA enforces code quality through multiple automated tools:

  1. Black - Automatic code formatting
  2. isort - Import statement sorting
  3. flake8 - Style guide enforcement (PEP 8)
  4. pylint - Comprehensive code analysis
  5. Pre-commit hooks - Automated checks before commits

Quick Start

Install Tools

# Install all code quality tools
pip install black isort flake8 pylint pre-commit

# Install pre-commit hooks
pre-commit install

Run All Quality Checks

# Run all checks
./scripts/code-quality.sh

# Auto-fix formatting
black --line-length 120 src/ tests/
isort --profile black --line-length 120 src/ tests/

Code Style Standards

General Principles

  1. Readability over cleverness - Code is read more than written
  2. Consistency - Follow established patterns
  3. Simplicity - Prefer simple, clear solutions
  4. Explicit over implicit - Be clear about intent
  5. Document when necessary - Explain "why", not "what"

Python Style Guide

We follow PEP 8 with some modifications:

  • Line length: 120 characters (not 79)
  • Indentation: 4 spaces (never tabs)
  • Quotes: Prefer double quotes for strings
  • Imports: Organized with isort (stdlib, third-party, local)

Naming Conventions

# Constants - UPPER_CASE
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30

# Classes - PascalCase
class TradingStrategy:
    pass

# Functions/methods - snake_case
def calculate_sharpe_ratio(returns):
    pass

# Variables - snake_case
current_position = 100
total_pnl = 1500.50

# Private/internal - leading underscore
def _internal_helper():
    pass

_private_variable = "internal use only"

# Special financial abbreviations (allowed exceptions)
px = 100  # price
qty = 50  # quantity
pnl = 200  # profit and loss
df = pd.DataFrame()  # dataframe (common in data science)

Imports Organization

# 1. Standard library imports
import os
import sys
from datetime import datetime
from typing import List, Dict

# 2. Third-party imports
import numpy as np
import pandas as pd
from django.db import models

# 3. Local application imports
from dsta.backtesting.engine import BacktestEngine
from dsta.strategies.base import Strategy

Docstrings

Use Google-style docstrings:

def calculate_position_size(
    account_balance: float,
    risk_percent: float,
    entry_price: float,
    stop_loss: float
) -> float:
    """Calculate position size based on risk parameters.

    Args:
        account_balance: Total account balance in dollars
        risk_percent: Percentage of account to risk (0-1)
        entry_price: Planned entry price
        stop_loss: Stop loss price

    Returns:
        Position size in shares/units

    Raises:
        ValueError: If stop_loss equals entry_price

    Example:
        >>> calculate_position_size(10000, 0.02, 100, 95)
        40.0
    """
    if entry_price == stop_loss:
        raise ValueError("Stop loss cannot equal entry price")

    risk_amount = account_balance * risk_percent
    price_risk = abs(entry_price - stop_loss)
    return risk_amount / price_risk

Tool Configuration

Black (Formatter)

Configuration in pyproject.toml:

[tool.black]
line-length = 120
target-version = ['py312']

Run:

# Check formatting
black --check src/ tests/

# Auto-format
black src/ tests/

isort (Import Sorter)

Configuration in pyproject.toml:

[tool.isort]
profile = "black"
line_length = 120

Run:

# Check imports
isort --check-only --profile black src/ tests/

# Auto-sort
isort --profile black src/ tests/

flake8 (Linter)

Configuration in .flake8:

[flake8]
max-line-length = 120
extend-ignore = E203, E501, W503
exclude = migrations, __pycache__, .venv, venv, build, dist

Run:

flake8 src/ tests/

Common flake8 errors:

  • E501: Line too long - refactor or split line
  • F401: Imported but unused - remove import
  • F841: Local variable assigned but never used - remove or use
  • E302: Expected 2 blank lines - add spacing
  • E266: Too many leading '#' for block comment - use one or two

pylint (Code Analysis)

Configuration in .pylintrc - see file for detailed settings.

Run:

# Full analysis
pylint src/

# Specific file
pylint src/backtesting/engine.py

# With config
pylint --rcfile=.pylintrc src/

Common pylint messages:

  • C0114: Missing module docstring - add module-level docstring
  • R0913: Too many arguments - refactor into class or dataclass
  • W0212: Access to protected member - use public interface
  • R0801: Similar lines - extract to shared function

Pre-commit Hooks

Pre-commit hooks automatically run checks before each commit.

Installation

# Install pre-commit
pip install pre-commit

# Install git hooks
pre-commit install

# Install hooks for commit messages (optional)
pre-commit install --hook-type commit-msg

Usage

# Run automatically on changed files at commit time
git commit -m "Your message"

# Run manually on all files
pre-commit run --all-files

# Run specific hook
pre-commit run black --all-files

# Skip hooks (use sparingly!)
git commit -m "Message" --no-verify

# Update hook versions
pre-commit autoupdate

Configured Hooks

Our .pre-commit-config.yaml includes:

  1. General checks: trailing whitespace, file endings, YAML/JSON syntax
  2. Black: Code formatting
  3. isort: Import sorting
  4. flake8: Linting
  5. pylint: Code quality (errors only)
  6. bandit: Security checks
  7. autoflake: Remove unused imports
  8. pyupgrade: Modernize Python syntax
  9. markdownlint: Markdown formatting
  10. Safety: Dependency vulnerability checks

CI/CD Integration

GitHub Actions

Create .github/workflows/code-quality.yml:

name: Code Quality

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: |
          pip install black isort flake8 pylint
      - name: Check formatting
        run: |
          black --check src/ tests/
          isort --check-only --profile black src/ tests/
      - name: Lint
        run: |
          flake8 src/ tests/
          pylint src/

Common Issues and Solutions

"Black would reformat file"

# Auto-fix
black src/ tests/

"Imports are incorrectly sorted"

# Auto-fix
isort --profile black src/ tests/

"Line too long"

# ❌ Bad - line too long
result = some_function(argument1, argument2, argument3, argument4, argument5, argument6, argument7)

# ✅ Good - split across lines
result = some_function(
    argument1,
    argument2,
    argument3,
    argument4,
    argument5,
    argument6,
    argument7,
)

"Too many arguments"

# ❌ Bad - too many arguments
def trade(symbol, quantity, price, stop_loss, take_profit, timeout, retry, log_level):
    pass

# ✅ Good - use dataclass or dict
from dataclasses import dataclass

@dataclass
class TradeParams:
    symbol: str
    quantity: int
    price: float
    stop_loss: float
    take_profit: float
    timeout: int = 30
    retry: int = 3
    log_level: str = "INFO"

def trade(params: TradeParams):
    pass

"Missing docstring"

# ❌ Bad - no documentation
def calc(a, b):
    return a * b + (a - b)

# ✅ Good - clear documentation
def calculate_pnl(entry_price: float, exit_price: float) -> float:
    """Calculate profit and loss for a trade.

    Args:
        entry_price: Price at which position was entered
        exit_price: Price at which position was exited

    Returns:
        Profit or loss amount
    """
    return exit_price - entry_price

Best Practices

1. Run Checks Before Committing

# Full check
./scripts/code-quality.sh

# Or use pre-commit
pre-commit run --all-files

2. Fix Issues Incrementally

Don't try to fix all issues at once. Focus on: 1. Security issues (bandit) 2. Errors (flake8, pylint E-level) 3. Formatting (black, isort) 4. Warnings (flake8, pylint W-level) 5. Refactoring (pylint R-level)

3. Ignore Sparingly

Only ignore checks when you have a good reason:

# Ignore specific line
x = eval(user_input)  # nosec B307 - validated earlier

# Ignore for function
# pylint: disable=too-many-arguments
def complex_function(a, b, c, d, e, f):
    pass
# pylint: enable=too-many-arguments

4. Keep Tools Updated

# Update pre-commit hooks
pre-commit autoupdate

# Update pip packages
pip install --upgrade black isort flake8 pylint

Maintenance

Weekly Tasks

  1. Run pre-commit autoupdate
  2. Review and address any new warnings
  3. Update tool versions in requirements

Before Each Release

  1. Run full quality check: ./scripts/code-quality.sh
  2. Review pylint score (target: 8.0+)
  3. Ensure pre-commit passes on all files
  4. Update this documentation if standards change

Resources

  • Black: https://black.readthedocs.io/
  • isort: https://pycqa.github.io/isort/
  • flake8: https://flake8.pycqa.org/
  • pylint: https://pylint.readthedocs.io/
  • pre-commit: https://pre-commit.com/
  • PEP 8: https://peps.python.org/pep-0008/
  • Google Python Style Guide: https://google.github.io/styleguide/pyguide.html

Support

For questions about code quality standards:

  1. Check this documentation
  2. Review existing code for examples
  3. Ask in pull request comments
  4. Open an issue with the code-quality label