.. _code_standards:
**************
Code Standards
**************
This document outlines the coding standards and style guidelines for quactuary. Following these standards helps ensure code quality, readability, and maintainability across the project.
.. contents:: Table of Contents
:local:
:depth: 2
Style Guide
===========
We follow the `PEP 8 Style Guide for Python Code `_ with some project-specific additions and clarifications.
Code Formatting
================
Black Formatter
---------------
We use `Black `_ for automatic code formatting:
.. code-block:: bash
# Format all Python files
black .
# Check formatting without changes
black --check .
# Format specific file
black quactuary/pricing.py
**Black configuration** (in ``pyproject.toml``):
.. code-block:: toml
[tool.black]
line-length = 88
target-version = ['py38']
include = '\.pyi?$'
extend-exclude = '''
/(
\.eggs
| \.git
| \.venv
| build
| dist
)/
'''
Line Length
-----------
* **Maximum line length**: 88 characters (Black default)
* **Docstring line length**: 79 characters for readability
* **Comments**: 72 characters for inline comments
Import Organization
===================
Organize imports in the following order:
1. **Standard library imports**
2. **Third-party imports**
3. **Local application imports**
Use `isort `_ for automatic import sorting:
.. code-block:: bash
# Sort imports
isort .
# Check import order
isort --check-only .
**Example import organization:**
.. code-block:: python
# Standard library
import datetime
import warnings
from typing import Optional, Union, List
# Third-party
import numpy as np
import pandas as pd
from scipy import stats
# Local imports
from quactuary.backend import BackendManager
from quactuary.distributions.frequency import FrequencyModel
from quactuary.utils.validation import validate_positive
Naming Conventions
==================
Variables and Functions
-----------------------
* Use ``snake_case`` for variables and functions
* Use descriptive names that clearly indicate purpose
* Avoid abbreviations unless they're standard in the domain
.. code-block:: python
# Good
portfolio_mean_loss = calculate_portfolio_statistics()
frequency_distribution = Poisson(lambda_=2.0)
# Avoid
pml = calc_stats()
freq_dist = Poisson(lambda_=2.0)
Constants
---------
* Use ``UPPER_CASE`` for module-level constants
* Group related constants together
.. code-block:: python
# Good
DEFAULT_N_SIMULATIONS = 10000
MAX_QUANTUM_QUBITS = 50
SUPPORTED_BACKENDS = ['classical', 'quantum']
Classes
-------
* Use ``PascalCase`` for class names
* Choose names that clearly indicate the class purpose
* Prefer composition over inheritance
.. code-block:: python
# Good
class PricingModel:
"""Portfolio pricing and risk analysis model."""
class ClassicalPricingStrategy:
"""Strategy for classical Monte Carlo calculations."""
Private Methods and Variables
-----------------------------
* Use single leading underscore for internal use
* Use double leading underscore only for name mangling when necessary
.. code-block:: python
class ExampleClass:
def __init__(self):
self.public_attribute = "visible"
self._internal_attribute = "internal use"
def public_method(self):
"""Public interface method."""
return self._internal_method()
def _internal_method(self):
"""Internal helper method."""
pass
Type Hints
==========
Use type hints for all public functions, methods, and class attributes:
.. code-block:: python
from typing import Optional, Union, List, Dict, Any
import numpy as np
import pandas as pd
def calculate_statistics(
portfolio: Portfolio,
n_sims: int = 10000,
confidence_level: float = 0.95,
include_samples: bool = False
) -> PricingResult:
"""Calculate portfolio risk statistics."""
pass
class PricingModel:
"""Portfolio pricing model."""
def __init__(self, portfolio: Portfolio) -> None:
self.portfolio: Portfolio = portfolio
self.results: Optional[PricingResult] = None
**Type hint guidelines:**
* Always include return type annotations
* Use ``Optional[T]`` instead of ``Union[T, None]``
* Import types from ``typing`` module
* Use ``Any`` sparingly - prefer specific types
Documentation Standards
========================
Docstring Format
----------------
We use `Google docstring format `_:
.. code-block:: python
def calculate_portfolio_var(
portfolio: Portfolio,
confidence_level: float = 0.95,
n_sims: int = 10000
) -> float:
"""
Calculate Value at Risk for a portfolio using Monte Carlo simulation.
This function estimates the VaR by simulating portfolio losses and
computing the specified quantile. The calculation uses either classical
Monte Carlo or quantum algorithms depending on the current backend.
Args:
portfolio (Portfolio): Portfolio containing policy information and
loss distributions. Must have at least one inforce bucket.
confidence_level (float): Confidence level for VaR calculation.
Must be between 0 and 1. Default is 0.95 (95% VaR).
n_sims (int): Number of Monte Carlo simulations to perform.
Higher values improve accuracy but increase computation time.
Default is 10,000.
Returns:
float: Value at Risk at the specified confidence level in the same
currency units as the portfolio.
Raises:
ValueError: If confidence_level is not between 0 and 1.
ValueError: If portfolio is empty or invalid.
RuntimeError: If simulation fails to converge.
Examples:
Basic VaR calculation:
>>> portfolio = Portfolio(policies_df)
>>> var_95 = calculate_portfolio_var(portfolio, 0.95)
>>> print(f"95% VaR: ${var_95:,.2f}")
High-precision calculation:
>>> var_99 = calculate_portfolio_var(
... portfolio,
... confidence_level=0.99,
... n_sims=100000
... )
Notes:
- VaR represents the loss threshold that will not be exceeded
with the specified probability
- Uses the current global backend (classical or quantum)
- For quantum calculations, n_sims may be adjusted internally
"""
**Docstring sections to include:**
* **Brief description**: One-line summary
* **Detailed description**: Multi-paragraph explanation if needed
* **Args**: All parameters with types and descriptions
* **Returns**: Return value type and description
* **Raises**: Exceptions that may be raised
* **Examples**: Code examples showing usage
* **Notes**: Additional important information
Class Documentation
-------------------
.. code-block:: python
class PricingModel:
"""
Portfolio pricing and risk analysis model.
This class provides a unified interface for calculating portfolio risk
measures using various computational backends. It supports both classical
Monte Carlo simulation and quantum-accelerated algorithms.
The model uses a strategy pattern to delegate calculations to different
implementations while maintaining a consistent interface. This allows
for runtime switching between computational approaches.
Attributes:
portfolio (Portfolio): The portfolio being analyzed.
strategy (PricingStrategy): Current calculation strategy.
backend_type (str): Type of computational backend in use.
Examples:
Basic usage:
>>> portfolio = Portfolio(policies_df)
>>> model = PricingModel(portfolio)
>>> result = model.simulate(n_sims=10000)
With custom strategy:
>>> strategy = ClassicalPricingStrategy(use_jit=True)
>>> model = PricingModel(portfolio, strategy=strategy)
Notes:
- Strategies can be swapped at runtime
- The model is stateless by design for thread safety
- All monetary values should be in consistent units
"""
Comments
========
Inline Comments
---------------
Use comments sparingly - prefer self-documenting code:
.. code-block:: python
# Good - explains non-obvious business logic
tail_alpha = 1 - confidence_level # VaR uses tail probability
# Good - explains complex calculation
# Apply Owen scrambling to improve QMC uniformity
scrambled_points = sobol_generator.random_scrambled()
# Avoid - states the obvious
n_sims = 10000 # Set number of simulations to 10000
TODO Comments
-------------
.. code-block:: python
# TODO(username): Add support for conditional VaR calculation
# FIXME: This approximation fails for heavy-tailed distributions
# NOTE: This uses a simplified approach - see issue #123
Error Handling
==============
Exception Handling
------------------
* Use specific exception types
* Provide helpful error messages
* Include context in error messages
.. code-block:: python
def validate_portfolio(portfolio: Portfolio) -> None:
"""Validate portfolio before calculations."""
if not portfolio.policies:
raise ValueError(
"Portfolio must contain at least one policy. "
"Received empty portfolio."
)
if portfolio.total_exposure <= 0:
raise ValueError(
f"Portfolio exposure must be positive. "
f"Got {portfolio.total_exposure}"
)
Custom Exceptions
-----------------
Create specific exception types for domain errors:
.. code-block:: python
class QuactuaryError(Exception):
"""Base exception for quactuary package."""
pass
class CalculationError(QuactuaryError):
"""Raised when numerical calculations fail."""
pass
class BackendError(QuactuaryError):
"""Raised when backend operations fail."""
pass
Performance Considerations
==========================
Efficient Code Patterns
------------------------
.. code-block:: python
# Good - use NumPy operations
losses = np.sum(frequency_samples * severity_samples, axis=1)
# Avoid - Python loops for large arrays
losses = []
for i in range(len(frequency_samples)):
losses.append(frequency_samples[i] * severity_samples[i])
Memory Management
-----------------
.. code-block:: python
# Good - process in chunks for large datasets
def process_large_portfolio(portfolio: Portfolio, chunk_size: int = 10000):
for chunk in portfolio.iter_chunks(chunk_size):
yield process_chunk(chunk)
# Good - use generators for large sequences
def simulate_losses(n_sims: int):
for i in range(n_sims):
yield simulate_single_loss()
Testing Code Quality
====================
Static Analysis
---------------
Use these tools to check code quality:
.. code-block:: bash
# Style checking
flake8 quactuary/
# Type checking
mypy quactuary/
# Security scanning
bandit -r quactuary/
# Complexity analysis
radon cc quactuary/ -a
Configuration files for these tools should be in ``pyproject.toml`` or dedicated config files.
Pre-commit Hooks
----------------
Set up pre-commit hooks to automatically check code quality:
.. code-block:: bash
# Install hooks
pre-commit install
# Run on all files
pre-commit run --all-files
Git Commit Standards
====================
Commit Message Format
---------------------
.. code-block:: text
type(scope): brief description
Longer description explaining what changed and why.
Include any breaking changes or migration notes.
Closes #123
Fixes #456
**Types:**
* ``feat``: New feature
* ``fix``: Bug fix
* ``docs``: Documentation changes
* ``style``: Code style changes (no logic changes)
* ``refactor``: Code refactoring
* ``test``: Adding or updating tests
* ``perf``: Performance improvements
**Examples:**
.. code-block:: text
feat(quantum): add quantum amplitude estimation for VaR
Implement QAE algorithm for Value at Risk calculations, providing
quadratic speedup over classical Monte Carlo for certain problem
structures. Includes comprehensive test suite and benchmarks.
Closes #123
.. code-block:: text
fix(pricing): handle edge case in compound distribution sampling
Fix issue where extremely small frequency parameters caused
numerical instability in Poisson sampling. Add validation
and improved error messages.
Fixes #456
Code Review Guidelines
======================
For Reviewers
-------------
* **Focus on correctness**: Does the code do what it's supposed to do?
* **Check performance**: Are there obvious performance issues?
* **Verify tests**: Is the code adequately tested?
* **Review documentation**: Are docstrings clear and complete?
* **Consider maintainability**: Is the code easy to understand and modify?
For Authors
-----------
* **Keep PRs focused**: One feature or fix per PR
* **Write clear descriptions**: Explain what changed and why
* **Include tests**: All new code should have tests
* **Update documentation**: Keep docs in sync with code changes
* **Be responsive**: Address review feedback promptly
Common Code Patterns
=====================
Error Checking
--------------
.. code-block:: python
def calculate_risk_measure(data: np.ndarray, alpha: float) -> float:
"""Calculate risk measure with proper validation."""
# Input validation
if not isinstance(data, np.ndarray):
data = np.asarray(data)
if len(data) == 0:
raise ValueError("Data array cannot be empty")
if not 0 < alpha < 1:
raise ValueError(f"Alpha must be between 0 and 1, got {alpha}")
# Calculation
return np.percentile(data, (1 - alpha) * 100)
Caching Results
---------------
.. code-block:: python
class ExpensiveCalculation:
def __init__(self):
self._cache: Dict[str, Any] = {}
def expensive_method(self, param: float) -> float:
"""Method with caching for expensive calculations."""
cache_key = f"expensive_{param}"
if cache_key not in self._cache:
# Perform expensive calculation
result = self._do_expensive_calculation(param)
self._cache[cache_key] = result
return self._cache[cache_key]
Backward Compatibility
======================
When making changes:
* **Deprecate before removing**: Use ``warnings.warn`` for deprecated features
* **Maintain API compatibility**: Avoid breaking changes in minor versions
* **Document breaking changes**: Include migration notes in release notes
.. code-block:: python
import warnings
def old_function_name(*args, **kwargs):
"""Deprecated function - use new_function_name instead."""
warnings.warn(
"old_function_name is deprecated and will be removed in v2.0. "
"Use new_function_name instead.",
DeprecationWarning,
stacklevel=2
)
return new_function_name(*args, **kwargs)
Following these standards helps ensure that quactuary remains a high-quality, maintainable codebase that's enjoyable for everyone to work with!