Extending FinLab Development Guide
Development Environment Setup
1. Fork and Clone the Project
# Fork finlab-python/finlab to your GitHub account
# Clone your fork
git clone https://github.com/YOUR_USERNAME/finlab.git
cd finlab
# Add upstream remote
git remote add upstream https://github.com/finlab-python/finlab.git
2. Install Development Dependencies
# Use uv for dependency management
pip install uv
# Install all development dependencies
uv sync --all-groups
# Or use pip
pip install -e ".[dev,docs,test]"
3. Run Tests
# Run all tests
uv run pytest
# Run specific tests
uv run pytest tests/backtest_test.py
# Run tests with coverage report
uv run pytest --cov=finlab --cov-report=html
Code Style Guide
Python Style
Follow PEP 8 and the Google Python Style Guide.
# Good example
def calculate_moving_average(prices, window=20):
"""Compute a moving average.
Args:
prices (pd.Series): price series
window (int): window size, defaults to 20
Returns:
pd.Series: moving-average series
"""
return prices.rolling(window).mean()
# Bad example
def calc_ma(p, w=20): # unclear naming
return p.rolling(w).mean() # missing docstring
Docstring Format
Use Google style:
def sim(position, resample='M', **kwargs):
"""Run a backtest.
Args:
position (pd.DataFrame): position signals
resample (str): rebalance frequency
**kwargs: additional parameters
Returns:
Report: backtest report object
Raises:
ValueError: when `position` has an invalid format
Examples:
```python
position = close > close.average(20)
report = sim(position, resample='M')
```
"""
pass
Extension Examples
1. Add a Custom Technical Indicator
Add a method to finlab/dataframe.py:
class FinlabDataFrame(pd.DataFrame):
# ... existing code
def custom_indicator(self, param1, param2=10):
"""Custom technical indicator.
Args:
param1 (float): parameter 1
param2 (int): parameter 2, defaults to 10
Returns:
FinlabDataFrame: computation result
"""
result = self.rolling(param2).apply(
lambda x: your_calculation(x, param1)
)
return FinlabDataFrame(result)
2. Add a Custom Analysis Module
Inherit from the Analysis class:
# finlab/analysis/custom_analysis.py
from finlab.analysis import Analysis
class CustomAnalysis(Analysis):
def calculate_trade_info(self, report):
"""Compute extra trade info."""
custom_data = ... # your logic
return [['custom_field', custom_data, 'entry_sig_date']]
def analyze(self, report):
"""Run the analysis."""
trades = report.get_trades()
result = ... # your analysis logic
return result
def display(self):
"""Render the result."""
return self.result
3. Add a Custom Market
Inherit from the Market class:
# finlab/markets/custom.py
from finlab.market import Market
import pandas as pd
class CustomMarket(Market):
@staticmethod
def get_name():
return 'custom_market'
def get_price(self, trade_at_price='close', adj=True):
# your data-loading logic
df = pd.read_csv('path/to/data.csv')
return df
Testing Guide
Test Structure
# tests/test_custom_feature.py
import unittest
from finlab import data
from finlab.dataframe import FinlabDataFrame
class TestCustomFeature(unittest.TestCase):
def setUp(self):
"""Set up before each test."""
self.close = data.get('price:收盤價')
def test_custom_indicator(self):
"""Test the custom indicator."""
result = self.close.custom_indicator(param1=0.5, param2=10)
# Check result type
self.assertIsInstance(result, FinlabDataFrame)
# Check result shape
self.assertEqual(result.shape, self.close.shape)
# Check result values
self.assertFalse(result.isna().all().all())
def tearDown(self):
"""Clean up after each test."""
pass
Contribution Workflow
1. Create a Branch
# Update main branch
git checkout main
git pull upstream main
# Create a new branch
git checkout -b feature/your-feature-name
2. Develop and Test
# Write your code
# ...
# Run tests
uv run pytest
# Run linter
uv run ruff check .
uv run ruff format .
3. Commit Your Code
# Commit changes
git add .
git commit -m "feat: add custom indicator"
# Push to your fork
git push origin feature/your-feature-name
4. Create a Pull Request
- Go to your fork on GitHub
- Click "New Pull Request"
- Fill in the PR description (explain the changes and their purpose)
- Wait for code review