Skip to content

Type Checking with MyPy

This document describes the static type checking setup for the DSTA project using MyPy.

Table of Contents

Overview

DSTA uses MyPy for static type checking to:

  • Catch bugs early: Type errors caught before runtime
  • Improve code quality: Better documentation and IDE support
  • Refactoring confidence: Safe large-scale changes
  • Team collaboration: Clear interfaces and expectations

Current Status

  • Configuration: mypy.ini (primary), pyproject.toml (fallback)
  • Coverage: Progressive - strict mode on core modules, gradual elsewhere
  • CI Pipeline: .github/workflows/type-check.yml

Configuration

MyPy Configuration (mypy.ini)

The project uses mypy.ini for configuration with the following strategy:

Global Settings (Moderate):

disallow_untyped_defs = False       # Allow untyped functions (for now)
ignore_missing_imports = True       # Ignore third-party without stubs
check_untyped_defs = True          # Check typed functions thoroughly
strict_optional = True              # Strict None checking

Strict Modules (High Quality): - src/backtesting/base.py - src/backtesting/events.py - src/backtesting/performance.py - src/analytics/metrics.py - src/trading/orders.py - src/trading/position.py

Third-Party Ignores:

[mypy-binance.*]
ignore_missing_imports = True

[mypy-pandas.*]
ignore_missing_imports = True

Running MyPy

Local Development

Check specific module:

mypy src/backtesting/base.py

Check entire module:

mypy src/backtesting/

Check all src:

mypy src/

With HTML report:

mypy src/ --html-report mypy-report

Using Virtual Environment

# Activate venv
source .venv/bin/activate

# Run mypy
mypy src/backtesting/ --config-file mypy.ini

Quick Check Script

Create scripts/check-types.sh:

#!/bin/bash
set -e

echo "🔍 Running MyPy type checking..."

# Core modules (strict)
echo "Checking backtesting..."
mypy src/backtesting/ --config-file mypy.ini

echo "Checking analytics..."
mypy src/analytics/ --config-file mypy.ini

echo "Checking trading..."
mypy src/trading/ --config-file mypy.ini

echo "✅ Type checking complete!"

Type Hints Guidelines

Function Signatures

Basic function:

from typing import List, Dict, Optional
from decimal import Decimal

def calculate_return(
    initial: Decimal,
    final: Decimal,
    periods: int
) -> Decimal:
    """Calculate annualized return."""
    return ((final / initial) ** (365 / periods)) - 1

Optional return:

from typing import Optional

def get_position(symbol: str) -> Optional[Position]:
    """Get position for symbol, None if not found."""
    return self.positions.get(symbol)

Multiple return types:

from typing import Union

def parse_value(value: str) -> Union[int, float, str]:
    """Parse value to int, float, or keep as string."""
    try:
        return int(value)
    except ValueError:
        try:
            return float(value)
        except ValueError:
            return value

Class Annotations

Data class:

from dataclasses import dataclass
from decimal import Decimal
from datetime import datetime

@dataclass
class Trade:
    """Represents a completed trade."""
    symbol: str
    entry_price: Decimal
    exit_price: Decimal
    quantity: Decimal
    entry_time: datetime
    exit_time: datetime
    pnl: Decimal

Class with methods:

from typing import List, Dict
from decimal import Decimal

class Portfolio:
    """Trading portfolio manager."""

    def __init__(self, initial_capital: Decimal) -> None:
        self.capital: Decimal = initial_capital
        self.positions: Dict[str, Position] = {}
        self.trades: List[Trade] = []

    def get_total_value(self) -> Decimal:
        """Calculate total portfolio value."""
        position_value = sum(
            pos.market_value 
            for pos in self.positions.values()
        )
        return self.capital + position_value

Type Aliases

Complex types:

from typing import Dict, List, Tuple
from decimal import Decimal

# Type aliases for clarity
PriceData = Dict[str, Decimal]
OHLCVData = Tuple[Decimal, Decimal, Decimal, Decimal, Decimal]
SymbolData = Dict[str, List[OHLCVData]]

def process_market_data(data: SymbolData) -> PriceData:
    """Process market data."""
    return {
        symbol: ohlcv[-1][3]  # Close price
        for symbol, ohlcv in data.items()
    }

Generic Types

Container types:

from typing import TypeVar, Generic, List

T = TypeVar('T')

class DataHandler(Generic[T]):
    """Generic data handler."""

    def __init__(self) -> None:
        self.data: List[T] = []

    def add(self, item: T) -> None:
        """Add item to handler."""
        self.data.append(item)

    def get_all(self) -> List[T]:
        """Get all items."""
        return self.data

Common Patterns

Django Models

from typing import Optional
from django.db import models
from decimal import Decimal

class Candlestick(models.Model):
    """Candlestick data model."""

    symbol: models.CharField
    timestamp: models.DateTimeField
    open: models.DecimalField

    def get_close_price(self) -> Decimal:
        """Get close price as Decimal."""
        return Decimal(str(self.close))

    @classmethod
    def get_latest(
        cls, 
        symbol: str, 
        limit: int = 100
    ) -> 'models.QuerySet[Candlestick]':
        """Get latest candlesticks."""
        return cls.objects.filter(
            symbol=symbol
        ).order_by('-timestamp')[:limit]

Pandas Integration

from typing import List
import pandas as pd
from decimal import Decimal

def calculate_moving_average(
    data: pd.DataFrame, 
    window: int
) -> pd.Series:
    """Calculate moving average."""
    return data['close'].rolling(window=window).mean()

def convert_to_dataframe(
    records: List[Dict[str, any]]
) -> pd.DataFrame:
    """Convert records to DataFrame."""
    return pd.DataFrame(records)

Async Functions

from typing import List, Optional
import asyncio

async def fetch_market_data(
    symbol: str, 
    timeout: float = 10.0
) -> Optional[Dict[str, any]]:
    """Fetch market data asynchronously."""
    try:
        # Async API call
        data = await api_client.get_ticker(symbol)
        return data
    except asyncio.TimeoutError:
        return None

CI/CD Integration

GitHub Actions Workflow

The type checking workflow (.github/workflows/type-check.yml) runs on: - Push to main or develop - Pull requests to main or develop

Workflow steps: 1. Checkout code 2. Set up Python 3.12 3. Install dependencies 4. Run MyPy on core modules 5. Generate HTML report 6. Upload report as artifact

Accessing reports: - Go to Actions tab in GitHub - Select workflow run - Download mypy-report artifact

Pre-commit Hook

Add to .pre-commit-config.yaml:

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy
        additional_dependencies: [types-requests, types-redis]
        args: [--config-file=mypy.ini]
        files: ^src/(backtesting|analytics|trading)/

Troubleshooting

Common Issues

Issue: "Module has no attribute"

error: Module has no attribute "OrderSide"
Solution: Check imports and ensure class/function is exported
# In __init__.py
from .orders import Order, OrderSide, OrderType

__all__ = ['Order', 'OrderSide', 'OrderType']

Issue: "Incompatible types in assignment"

error: Incompatible types in assignment (expression has type "int", variable has type "Decimal")
Solution: Use explicit type conversion
# Wrong
quantity: Decimal = 100

# Correct
quantity: Decimal = Decimal('100')

Issue: "Missing type parameters"

error: Missing type parameters for generic type "Dict"
Solution: Add type parameters
# Wrong
def get_prices() -> Dict:

# Correct  
def get_prices() -> Dict[str, Decimal]:

Ignoring Specific Errors

Single line:

result = legacy_function()  # type: ignore[no-untyped-call]

Entire function:

# mypy: disable-error-code="no-untyped-def"
def legacy_function():
    pass

Entire file:

# mypy: ignore-errors

Installing Type Stubs

# Install common type stubs
pip install types-requests
pip install types-redis
pip install types-pytz
pip install pandas-stubs
pip install django-stubs

Progress Tracking

Current Type Coverage

Run to check progress:

mypy src/ --html-report mypy-report

Open mypy-report/index.html to see: - Files checked - Type coverage percentage - Error locations

Migration Plan

Phase 1: Core Modules (Complete) - ✅ backtesting/base.py - ✅ backtesting/events.py - ✅ trading/orders.py - ✅ trading/position.py

Phase 2: Analytics (In Progress) - 🔄 analytics/metrics.py - ⬜ analytics/indicators.py - ⬜ analytics/signals.py

Phase 3: Trading Logic (Planned) - ⬜ trading/executor.py - ⬜ trading/risk_limits.py - ⬜ trading/stops.py

Phase 4: API Layer (Future) - ⬜ api/views.py - ⬜ api/serializers.py

Best Practices

  1. Start with return types: Easier than full type hints
  2. Use type aliases: Make complex types readable
  3. Leverage IDE support: VS Code, PyCharm auto-complete
  4. Incremental adoption: Don't type everything at once
  5. Run locally first: Fix errors before CI
  6. Document exceptions: Use # type: ignore with comments
  7. Update as you go: Add types when modifying code

Resources

Support

For questions or issues: 1. Check this documentation 2. Review MyPy error codes: https://mypy.readthedocs.io/en/stable/error_codes.html 3. Search existing issues in repository 4. Ask in team Slack #dev-python channel