Skip to content

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

  1. Go to your fork on GitHub
  2. Click "New Pull Request"
  3. Fill in the PR description (explain the changes and their purpose)
  4. Wait for code review

Reference Resources