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.
CashAmountis 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
FeeModelsubclasses to separate concerns from strategy code. - Parameterize commission rates via constructors to enable rapid experimentation without code changes.
- Use
CashAmountwith the correct currency code to prevent accounting errors in multi-currency backtests. - Attach custom models via
SetFeeModel()immediately after security creation in yourInitializemethod. - Reference the
paperswithbacktest/awesome-systematic-tradingrepository 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →