How to Implement Custom Fee Models in Backtesting Frameworks: A QuantConnect Guide

Implement custom fee models in QuantConnect by inheriting from FeeModel, overriding GetOrderFee to return a CashAmount, and attaching the model to each security via SetFeeModel() to ensure realistic transaction cost simulation.

Accurate backtesting requires realistic transaction cost modeling beyond default assumptions. The paperswithbacktest/awesome-systematic-trading repository demonstrates how to implement custom fee models in backtesting frameworks by encapsulating commission logic into reusable classes that derive from the FeeModel abstraction. This pattern separates cost calculations from strategy logic, enabling precise control over how fees impact performance metrics across different securities and markets.

Understanding the FeeModel Abstraction

QuantConnect exposes a FeeModel base class that defines how transaction costs are calculated for each order. The framework calls the GetOrderFee method for every order execution, passing an OrderFeeParameters object containing the security and order details. By overriding this single method in a derived class, you can implement everything from fixed commissions to complex tiered pricing structures without modifying your strategy's core logic.

Six Best Practices for Custom Fee Models

1. Encapsulate Fee Logic in a Dedicated Class

Create a separate class that inherits from FeeModel and implement only the GetOrderFee method. This isolation prevents fee calculations from cluttering your algorithm code and makes the model testable and portable across different strategies.

2. Parameterize Fee Rates Instead of Hard-Coding

Store commission rates as class attributes or constructor parameters rather than magic numbers. This allows you to experiment with different broker fee structures—such as 4 basis points versus 5 basis points—without touching the algorithm implementation.

3. Handle Currencies Correctly

Always return fees wrapped in a CashAmount object with the appropriate currency code, typically "USD" for US equities. Using CashAmount(fee, currency) ensures proper currency conversion and prevents accounting errors when backtesting multi-currency portfolios.

4. Attach Models During Initialization

Call security.SetFeeModel(CustomFeeModel()) immediately after adding the security to your algorithm, typically within the Initialize method. This ensures every order processed for that security uses your custom logic rather than default commission assumptions.

5. Maintain Deterministic Calculations

Avoid random components in fee calculations unless explicitly modeling stochastic costs like slippage or market impact. Deterministic fees ensure that backtest results remain reproducible when rerunning the same strategy parameters.

6. Document Fee Assumptions

Include inline comments describing the source and rationale for your commission rates, such as "0.5 bps typical broker fee" or "exchange minimum plus clearing fee." This documentation helps future maintainers understand whether the model represents retail or institutional pricing tiers.

Complete Implementation Example

Here is a reusable, parameterized fee model that applies a fixed commission rate in basis points:


# ----------------------------------------------------------------------

# Example: Parametrised custom fee model (re‑usable across strategies)

# ----------------------------------------------------------------------

from AlgorithmImports import *

class FixedCommissionFeeModel(FeeModel):
    """Applies a fixed commission rate (in basis points) to every order."""
    def __init__(self, rate_bps: float = 5.0):
        # Convert basis points to a decimal proportion (e.g., 5 bps → 0.00005)

        self._rate = rate_bps / 1_00_000

    def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
        # fee = price × quantity × rate

        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * self._rate
        return OrderFee(CashAmount(fee, "USD"))

# ----------------------------------------------------------------------

# Usage inside a QuantConnect algorithm

# ----------------------------------------------------------------------

class MyStrategy(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetCash(1_000_000)

        # Add an equity and attach the custom fee model

        equity = self.AddEquity("SPY", Resolution.Daily)
        equity.SetFeeModel(FixedCommissionFeeModel(rate_bps=4.0))   # 4 bps

        # Add another security with the default 5 bps fee model

        another = self.AddEquity("EWJ", Resolution.Daily)
        another.SetFeeModel(FixedCommissionFeeModel())             # uses default 5 bps

Key implementation details include:

  • The fee rate is configurable via the constructor (rate_bps).
  • The model is stateless aside from the rate, making it safe for parallel backtests.
  • CashAmount is used with the correct currency ("USD"), matching QuantConnect's expectations.

Real-World Patterns in the Repository

The paperswithbacktest/awesome-systematic-trading repository demonstrates these practices across multiple strategy implementations. In static/strategies/value-factor-effect-within-countries.py, lines 42-46 define a minimal CustomFeeModel that multiplies security price by order quantity and a fixed rate of 0.00005 (5 basis points).

Similarly, static/strategies/momentum-factor-effect-in-stocks.py at lines 17-21 reuses this exact pattern, illustrating a consistent approach to fee modeling throughout the codebase. The same structure appears in static/strategies/short-term-reversal-in-stocks.py and static/strategies/fx-carry-trade.py, demonstrating how a single fee model implementation can service diverse strategy types from equity factors to foreign exchange carry trades.

Summary

  • Encapsulate fee logic in dedicated FeeModel subclasses to separate concerns from strategy code.
  • Parameterize commission rates via constructors to enable rapid experimentation without code changes.
  • Use CashAmount with the correct currency code to prevent accounting errors in multi-currency backtests.
  • Attach custom models via SetFeeModel() immediately after security creation in your Initialize method.
  • Reference the paperswithbacktest/awesome-systematic-trading repository for consistent implementation patterns across value, momentum, and FX strategies.

Frequently Asked Questions

How do I convert basis points to the decimal format expected by QuantConnect fee models?

Divide your basis points value by 1,000,000 (or use rate_bps / 1_00_000 as shown in the repository examples). For instance, 5 basis points converts to 0.00005, which represents the decimal multiplier applied to the total order value to calculate the commission fee.

Can I assign different fee models to different securities in the same algorithm?

Yes, you can instantiate different FeeModel subclasses or pass different parameters to the same class when calling SetFeeModel() for each security. This allows you to model varying commission structures for equities versus forex, or apply institutional discounts to certain assets while charging retail rates for others.

Why should I avoid hard-coding commission rates in my strategy code?

Hard-coded rates require code modifications and redeployment to test different broker scenarios, breaking the separation of concerns between trading logic and execution costs. Parameterized fee models allow you to run systematic sweeps across commission levels to assess strategy sensitivity to transaction costs without altering the underlying algorithm.

What currency should I use for CashAmount when trading international equities?

Always match the CashAmount currency to the security's quote currency as provided by parameters.Security.QuoteCurrency.Symbol. For US equities traded in USD, use "USD", but for international assets, ensure you reference the correct three-letter ISO code to prevent currency conversion errors in your backtest P&L calculations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →