Developer Guide¶
Welcome to the Neutryx Core developer guide! This document covers everything you need to know to contribute to the project.
Table of Contents¶
- Development Setup
- Code Style and Standards
- Testing
- Contributing Workflow
- Adding New Features
- Documentation
- Release Process
Development Setup¶
Prerequisites¶
- Python 3.10 or higher
- Git
- GPU (optional, but recommended for performance testing)
Setting Up Development Environment¶
# Clone the repository
git clone https://github.com/neutryx-lab/neutryx-core.git
cd neutryx-core
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install development dependencies
pip install -r requirements.txt
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
# Verify setup
pytest -q
IDE Setup¶
VS Code¶
Recommended extensions: - Python - Pylance - Ruff - Test Explorer
.vscode/settings.json:
{
"python.linting.enabled": true,
"python.linting.ruffEnabled": true,
"python.formatting.provider": "black",
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
PyCharm¶
- Open project in PyCharm
- Configure interpreter: Settings → Project → Python Interpreter
- Enable pytest: Settings → Tools → Python Integrated Tools → Testing
- Install Ruff plugin
Code Style and Standards¶
Python Style Guide¶
We follow PEP 8 with some modifications:
- Line length: 100 characters (not 79)
- Use double quotes for strings
- Use type hints for all function signatures
Code Formatting¶
We use Ruff for linting and Black for formatting:
# Format code
black src/neutryx tests
# Lint code
ruff check src/neutryx tests
# Auto-fix linting issues
ruff check --fix src/neutryx tests
Type Hints¶
Always use type hints:
from typing import Tuple, Optional
import jax.numpy as jnp
def price_option(
spot: float,
strike: float,
maturity: float,
rate: float,
volatility: float,
option_type: str = "call"
) -> float:
"""
Price a European option using Black-Scholes.
Args:
spot: Current spot price
strike: Strike price
maturity: Time to maturity in years
rate: Risk-free rate
volatility: Volatility (annualized)
option_type: 'call' or 'put'
Returns:
Option price
Raises:
ValueError: If option_type is invalid
"""
if option_type not in ["call", "put"]:
raise ValueError(f"Invalid option_type: {option_type}")
# Implementation...
return price
Documentation Standards¶
Every public function must have a docstring:
def calculate_var(
returns: jnp.ndarray,
confidence: float = 0.95
) -> float:
"""
Calculate Value at Risk using historical simulation.
This function computes VaR by sorting historical returns and
finding the appropriate quantile.
Args:
returns: Array of historical returns
confidence: Confidence level (e.g., 0.95 for 95% VaR)
Returns:
VaR as a positive number
Examples:
>>> returns = jnp.array([-0.02, -0.01, 0.01, 0.02, 0.03])
>>> var_95 = calculate_var(returns, confidence=0.95)
>>> print(f"VaR(95%): {var_95:.4f}")
VaR(95%): 0.0180
References:
- Jorion, P. (2006). Value at Risk. McGraw-Hill.
"""
sorted_returns = jnp.sort(returns)
index = int((1 - confidence) * len(returns))
return -sorted_returns[index]
Testing¶
Running Tests¶
# Run all tests
pytest
# Run specific test file
pytest src/neutryx/tests/test_models.py
# Run tests with coverage
pytest --cov=neutryx --cov-report=html
# Run tests in parallel
pytest -n auto
# Run only fast tests (skip slow Monte Carlo tests)
pytest -m "not slow"
Writing Tests¶
Unit Tests¶
# src/neutryx/tests/test_pricing.py
import pytest
import jax.numpy as jnp
from neutryx.models.bs import price
class TestBlackScholes:
"""Test Black-Scholes pricing"""
def test_call_option_atm(self):
"""Test at-the-money call option"""
S, K, T = 100.0, 100.0, 1.0
r, q, sigma = 0.05, 0.02, 0.20
price_call = price(S, K, T, r, q, sigma, "call")
# Check price is reasonable
assert 0 < price_call < S
assert price_call == pytest.approx(9.625, rel=0.01)
def test_put_call_parity(self):
"""Test put-call parity holds"""
S, K, T = 100.0, 100.0, 1.0
r, q, sigma = 0.05, 0.02, 0.20
call = price(S, K, T, r, q, sigma, "call")
put = price(S, K, T, r, q, sigma, "put")
# C - P = S*e^(-qT) - K*e^(-rT)
lhs = call - put
rhs = S * jnp.exp(-q * T) - K * jnp.exp(-r * T)
assert lhs == pytest.approx(rhs, abs=1e-10)
@pytest.mark.parametrize("option_type", ["call", "put"])
def test_positive_prices(self, option_type):
"""Test that prices are always positive"""
S, K, T = 100.0, 100.0, 1.0
r, q, sigma = 0.05, 0.02, 0.20
price_val = price(S, K, T, r, q, sigma, option_type)
assert price_val > 0
def test_invalid_option_type(self):
"""Test error handling for invalid option type"""
with pytest.raises(ValueError, match="Invalid option_type"):
price(100.0, 100.0, 1.0, 0.05, 0.02, 0.20, "invalid")
Integration Tests¶
# src/neutryx/tests/integration/test_pricing_workflow.py
import pytest
from neutryx.models.bs import BlackScholesModel
from neutryx.products.options import VanillaOption
from neutryx.market.data import MarketData
class TestPricingWorkflow:
"""Test end-to-end pricing workflow"""
def test_complete_pricing_pipeline(self):
"""Test complete pricing from market data to result"""
# Setup market data
market = MarketData(
spot=100.0,
rate=0.05,
dividend=0.02,
volatility=0.20
)
# Create product
option = VanillaOption(
strike=100.0,
maturity=1.0,
option_type="call"
)
# Create model and price
model = BlackScholesModel()
price = model.price(option, market)
# Verify result
assert price > 0
assert price < market.spot
# Calculate Greeks
greeks = model.greeks(option, market)
assert 0 < greeks['delta'] < 1
assert greeks['gamma'] > 0
assert greeks['vega'] > 0
Performance Tests¶
# src/neutryx/tests/test_performance.py
import pytest
import time
import jax
import jax.numpy as jnp
from neutryx.models.bs import price
@pytest.mark.benchmark
class TestPerformance:
"""Performance benchmarks"""
def test_batch_pricing_speed(self, benchmark):
"""Benchmark batch pricing performance"""
spots = jnp.ones(10000) * 100.0
strikes = jnp.ones(10000) * 100.0
maturities = jnp.ones(10000) * 1.0
@jax.jit
def price_batch(S, K, T):
return jax.vmap(
lambda s, k, t: price(s, k, t, 0.05, 0.02, 0.20, "call")
)(S, K, T)
# Warmup
_ = price_batch(spots, strikes, maturities).block_until_ready()
# Benchmark
result = benchmark(
lambda: price_batch(spots, strikes, maturities).block_until_ready()
)
# Should price 10k options in < 100ms
assert benchmark.stats['mean'] < 0.1
@pytest.mark.slow
def test_monte_carlo_convergence(self):
"""Test Monte Carlo convergence rate"""
from neutryx.core.engine import monte_carlo_price
path_counts = [1000, 5000, 10000, 50000]
analytical = 9.625
for n_paths in path_counts:
mc_price = monte_carlo_price(
S0=100, K=100, T=1.0, r=0.05, sigma=0.20,
n_paths=n_paths
)
error = abs(mc_price - analytical)
expected_error = 5.0 / np.sqrt(n_paths) # ~5σ error bound
assert error < expected_error
Test Fixtures¶
# src/neutryx/tests/conftest.py
import pytest
import jax.numpy as jnp
@pytest.fixture
def market_data():
"""Standard market data fixture"""
return {
"spot": 100.0,
"strike": 100.0,
"maturity": 1.0,
"rate": 0.05,
"dividend": 0.02,
"volatility": 0.20
}
@pytest.fixture
def sample_returns():
"""Sample return data for testing"""
return jnp.array([-0.03, -0.02, -0.01, 0.0, 0.01, 0.02, 0.03])
@pytest.fixture(scope="session")
def test_database():
"""Database fixture for integration tests"""
# Setup
db = create_test_database()
yield db
# Teardown
db.drop_all()
Contributing Workflow¶
Branch Strategy¶
main: Production-ready codedevelop: Integration branch for featuresfeature/*: New featuresbugfix/*: Bug fixeshotfix/*: Critical production fixes
Contribution Process¶
- Fork and Clone
git clone https://github.com/YOUR_USERNAME/neutryx-core.git
cd neutryx-core
git remote add upstream https://github.com/neutryx-lab/neutryx-core.git
- Create Feature Branch
git checkout -b feature/my-new-feature develop
- Make Changes
# Edit files
vim src/neutryx/models/new_model.py
# Add tests
vim src/neutryx/tests/test_new_model.py
# Format code
black src/neutryx tests
ruff check --fix src/neutryx tests
- Commit Changes
git add src/neutryx/models/new_model.py
git add src/neutryx/tests/test_new_model.py
git commit -m "feat: add new pricing model
- Implement XYZ model
- Add comprehensive tests
- Update documentation
Closes #123"
Commit Message Convention:
- feat: New feature
- fix: Bug fix
- docs: Documentation only
- style: Code style (formatting, etc.)
- refactor: Code refactoring
- test: Adding tests
- chore: Maintenance tasks
- Push and Create PR
git push origin feature/my-new-feature
Then create Pull Request on GitHub.
Pull Request Checklist¶
- [ ] Code follows style guidelines
- [ ] All tests pass (
pytest) - [ ] New code has tests (>80% coverage)
- [ ] Documentation is updated
- [ ] Commit messages follow convention
- [ ] No merge conflicts with
develop - [ ] PR description explains changes
- [ ] Links to related issues
Adding New Features¶
Adding a New Model¶
- Create Model Class
# src/neutryx/models/my_model.py
from neutryx.models.base import Model
import jax.numpy as jnp
class MyModel(Model):
"""
My custom pricing model.
This model implements...
References:
- Author (Year). Paper Title. Journal.
"""
def __init__(self, **params):
self.params = params
@jax.jit
def price(self, spot, strike, maturity, **kwargs):
"""Price an option"""
# Implementation
return price
def calibrate(self, market_data, **kwargs):
"""Calibrate model to market data"""
# Implementation
return calibrated_params
- Add Tests
# src/neutryx/tests/test_my_model.py
import pytest
from neutryx.models.my_model import MyModel
class TestMyModel:
def test_pricing(self):
model = MyModel(param1=1.0, param2=2.0)
price = model.price(100, 100, 1.0)
assert price > 0
def test_calibration(self):
model = MyModel()
params = model.calibrate(market_data)
assert params is not None
- Add Documentation
# docs/models/my_model.md
- Update Exports
# src/neutryx/models/__init__.py
from .my_model import MyModel
__all__ = ["MyModel", ...]
Adding a New Product¶
# src/neutryx/products/my_product.py
from neutryx.products.base import Product
import jax.numpy as jnp
class MyProduct(Product):
"""
Custom derivative product.
Args:
param1: Description
param2: Description
"""
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
def payoff(self, paths):
"""Calculate payoff given price paths"""
# Implementation
return payoff
def price(self, model, market_data):
"""Price product using model"""
paths = model.simulate(market_data)
payoff = self.payoff(paths)
return self.present_value(payoff, market_data.rate)
Documentation¶
Building Documentation¶
# Install MkDocs
pip install mkdocs mkdocs-material
# Build documentation
mkdocs build
# Serve locally
mkdocs serve
# Visit http://localhost:8000
# Deploy to GitHub Pages
mkdocs gh-deploy
Documentation Structure¶
docs/
├── index.md # Home page
├── getting_started.md # Quick start guide
├── overview.md # Project overview
├── tutorials.md # Tutorials
├── architecture.md # Architecture guide
├── performance_tuning.md # Performance guide
├── troubleshooting.md # Troubleshooting
├── developer_guide.md # This file
├── api_reference.md # API docs
├── models/ # Model documentation
│ ├── index.md
│ └── black_scholes.md
└── products/ # Product documentation
├── index.md
└── vanilla_options.md
Release Process¶
Version Numbering¶
We use Semantic Versioning: - MAJOR.MINOR.PATCH (e.g., 1.2.3) - MAJOR: Breaking changes - MINOR: New features (backward compatible) - PATCH: Bug fixes
Release Checklist¶
- Update Version
# src/neutryx/__init__.py
__version__ = "0.2.0"
- Update Changelog
# docs/project/CHANGELOG.md
## [0.2.0] - 2025-01-15
### Added
- New Heston model implementation
- Real-time market data integration
### Fixed
- Monte Carlo convergence issue (#123)
### Changed
- Improved calibration performance
- Run Full Test Suite
pytest --cov=neutryx --cov-report=html
pytest --benchmark-only
- Build and Test Package
python -m build
pip install dist/neutryx_core-0.2.0-py3-none-any.whl
python -c "import neutryx; print(neutryx.__version__)"
- Create Git Tag
git tag -a v0.2.0 -m "Release version 0.2.0"
git push origin v0.2.0
- Create GitHub Release
- Go to GitHub Releases
- Create new release from tag
- Add release notes
-
Attach built packages
-
Publish to PyPI (if applicable)
python -m twine upload dist/*
Code Review Guidelines¶
For Authors¶
- Keep PRs focused and small (<500 lines)
- Write clear PR descriptions
- Respond to feedback promptly
- Update PR based on comments
For Reviewers¶
Check for: - [ ] Code correctness - [ ] Test coverage - [ ] Performance implications - [ ] Security concerns - [ ] Documentation quality - [ ] Code style compliance
Community¶
- GitHub Discussions: Ask questions and share ideas
- Issues: Report bugs and request features
- Discord (coming soon): Chat with developers
Resources¶
Thank you for contributing to Neutryx Core!