Docs
Open Studio

Architecture

Extending FinLab

On this page6

This page shows how to add features to the FinLab source code and how to contribute your changes back.

What you want to doWhere to start
Add a technical indicatorAdd a method to FinlabDataFrame in finlab/dataframe.py
Add an analysis moduleInherit from the Analysis class
Add a marketInherit from the Market class
Contribute your changesFork → create a branch → develop and test → commit → pull request

For how the modules fit together, see Module architecture.

Set up a development environment

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

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]"

Run the 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

Add your extension

Add a custom technical indicator

Add a method to FinlabDataFrame in 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)

Add a custom analysis module

Inherit from the Analysis class and implement calculate_trade_info(), analyze() and display(). For how the existing modules are used, see Analysis modules.

# 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

Add a custom market

Inherit from the Market class and provide the market's name (get_name()) and prices (get_price()). For the full method, see Custom markets.

# 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

Write tests for your feature

Tests live in tests/ and check the result's type, shape and values:

# 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."""
        data.set_market('us')
        self.close = data.get('price:adj_close')

    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

Follow the code style

Follow PEP 8 and the Google Python Style Guide: use clear names and give every function a docstring.

# 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: Google style

Docstrings use Google style, with Args, Returns, Raises and Examples sections:

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
        data.set_market('us')
        close = data.get('price:adj_close')
        position = close > close.average(20)
        report = sim(position, resample='M')
        ```
    """
    pass

Send your changes back to FinLab

Create a branch

# Update main branch
git checkout main
git pull upstream main

# Create a new branch
git checkout -b feature/your-feature-name

Develop and test

# Write your code
# ...

# Run tests
uv run pytest

# Run linter
uv run ruff check .
uv run ruff format .

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

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

Next steps

  • Module architecture How the modules divide the work, and which classes to subclass.
  • Analysis modules How the existing analysis modules work; read this before writing your own.
  • Custom markets Build a market object from your own data and backtest it.

The source is in the GitHub repository; report problems in the issue tracker.

↑↓ Move Enter Open Esc Close