Type Checking with MyPy¶
This document describes the static type checking setup for the DSTA project using MyPy.
Table of Contents¶
- Overview
- Configuration
- Running MyPy
- Type Hints Guidelines
- Common Patterns
- CI/CD Integration
- Troubleshooting
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:
Running MyPy¶
Local Development¶
Check specific module:
Check entire module:
Check all src:
With HTML report:
Using Virtual Environment¶
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"
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"
Solution: Use explicit type conversionIssue: "Missing type parameters"
Solution: Add type parametersIgnoring Specific Errors¶
Single line:
Entire function:
Entire file:
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:
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¶
- Start with return types: Easier than full type hints
- Use type aliases: Make complex types readable
- Leverage IDE support: VS Code, PyCharm auto-complete
- Incremental adoption: Don't type everything at once
- Run locally first: Fix errors before CI
- Document exceptions: Use
# type: ignorewith comments - 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