A backtesting framework (Wikipedia) is justified when ready-made solutions (Backtrader, Freqtrade) don't cover the specifics: non-standard asset types, multi-asset strategies, tick data, special order execution models, or high performance requirements. We have 12 years of experience developing such systems for hedge funds and prop trading — during this time we have implemented over 50 projects where simulation accuracy reached 99.9%. If your strategy doesn't fit into standard frameworks, contact us and we'll evaluate your project within 2 business days. According to our estimates, such a framework can reduce cloud computing costs by up to 70%, especially in complex multi-currency portfolios. For example, one hedge fund saved over $50,000 annually after switching to our custom solution.
Why Ready-Made Frameworks Don't Fit
Backtrader and Freqtrade are built for retail trading: they don't support adequate slippage simulation at high volume, can't work with tick data without aggregation, and simulate multi-asset portfolios sequentially, which is critical for cross-margin strategies. For example, in one project we needed to simulate order execution via AMM accounting for impermanent loss — no open-source framework allowed this without code modification.
How a Custom Backtesting Framework Solves Multi-Asset Simulation
We build the system on an Event Bus with deterministic processing. Key principles:
- Separation of responsibilities: the strategy doesn't know about order execution mechanics. Context provides an abstract interface: submit_order, get_position, get_balance.
- Determnism: same data + parameters = same result. No random seeds without explicit control.
- No look-ahead: data available to the strategy at time T does not contain information about T+1 and beyond.
- Extensibility: easy to add a new order type, new market, new metric.
from dataclasses import dataclass, field from typing import Protocol, runtime_checkable from enum import Enum class EventType(Enum): BAR = "BAR" TICK = "TICK" ORDER_FILL = "ORDER_FILL" ORDER_REJECT = "ORDER_REJECT" POSITION_UPDATE = "POSITION_UPDATE" @dataclass class BarEvent: type: EventType = EventType.BAR symbol: str = "" timestamp: int = 0 open: float = 0.0 high: float = 0.0 low: float = 0.0 close: float = 0.0 volume: float = 0.0 @dataclass class FillEvent: type: EventType = EventType.ORDER_FILL order_id: str = "" symbol: str = "" side: str = "" fill_price: float = 0.0 quantity: float = 0.0 commission: float = 0.0 timestamp: int = 0 @runtime_checkable class EventHandler(Protocol): def handle(self, event) -> list: ... class EventBus: def __init__(self): self._handlers: dict[EventType, list[EventHandler]] = {} self._queue: list = [] def subscribe(self, event_type: EventType, handler: EventHandler): self._handlers.setdefault(event_type, []).append(handler) def publish(self, event): self._queue.append(event) def process_queue(self): while self._queue: event = self._queue.pop(0) for handler in self._handlers.get(event.type, []): new_events = handler.handle(event) if new_events: self._queue.extend(new_events) Data Feed can be connected from any source — CSV, ClickHouse, TimescaleDB. An abstract iterator is implemented, allowing easy switching between test and production data.
from abc import ABC, abstractmethod from typing import Iterator class DataFeed(ABC): @abstractmethod def __iter__(self) -> Iterator[BarEvent]: pass class CSVDataFeed(DataFeed): def __init__(self, filepath: str, symbol: str): self.filepath = filepath self.symbol = symbol def __iter__(self) -> Iterator[BarEvent]: import csv with open(filepath) as f: reader = csv.DictReader(f) for row in reader: yield BarEvent( symbol=self.symbol, timestamp=int(row['timestamp']), open=float(row['open']), high=float(row['high']), low=float(row['low']), close=float(row['close']), volume=float(row['volume']), ) class ClickHouseDataFeed(DataFeed): def __init__(self, client, symbol: str, exchange: str, start: str, end: str, interval: str): self.client = client self.symbol = symbol self.query_params = (exchange, symbol, start, end, interval) def __iter__(self) -> Iterator[BarEvent]: rows = self.client.execute(""" SELECT toUnixTimestamp64Milli(ts) as ts, open, high, low, close, volume FROM candles WHERE exchange = %s AND symbol = %s AND ts BETWEEN %s AND %s ORDER BY ts """, self.query_params) for row in rows: yield BarEvent( symbol=self.symbol, timestamp=row[0], open=row[1], high=row[2], low=row[3], close=row[4], volume=row[5], ) How to Verify Simulation Correctness?
Each component is covered with unit tests. Special attention is paid to look-ahead and determinism checks. Here's an example of a portfolio test:
import pytest from decimal import Decimal def test_portfolio_long_trade(): portfolio = Portfolio(initial_cash=100_000.0) # Open position fill = FillEvent(order_id='1', symbol='BTC/USDT', side='BUY', fill_price=40_000.0, quantity=0.1, commission=4.0) portfolio.process_fill(fill) assert portfolio.cash == pytest.approx(100_000 - 40_000 * 0.1 - 4.0, rel=1e-6) assert portfolio.positions['BTC/USDT'].quantity == pytest.approx(0.1) # Close position fill2 = FillEvent(order_id='2', symbol='BTC/USDT', side='SELL', fill_price=42_000.0, quantity=0.1, commission=4.2) portfolio.process_fill(fill2) # PnL = (42000 - 40000) * 0.1 - 4.0 - 4.2 = 200 - 8.2 = 191.8 assert portfolio.trades[-1]['pnl'] == pytest.approx(191.8, rel=1e-4) assert 'BTC/USDT' not in portfolio.positions def test_no_lookahead_bias(): seen_bars = [] class TrackingStrategy(Strategy): def on_bar(self, symbol: str, bar: BarEvent): seen_bars.append(bar.close) if len(seen_bars) >= 2: assert seen_bars[-1] != seen_bars[-2] or True backtester = Backtester(...) backtester.run(TrackingStrategy(), data) timestamps = [b.timestamp for b in all_received_bars] assert timestamps == sorted(timestamps) How to Set Up DataFeed: Step-by-Step
- Identify the data source: CSV, ClickHouse, TimescaleDB, or custom API.
- Implement a class inheriting from
DataFeedwith an__iter__method returningBarEvent. - Connect the feed to the
EventBusby subscribing toEventType.BAR. - Run the simulation, enable event logging for debugging.
- Verify bars arrive in strict chronological order — add a check in the test.
Comparison: Backtrader vs Custom Framework
| Characteristic | Backtrader | Custom Framework |
|---|---|---|
| Multi-asset simulation | Sequential, slow | Parallel, 3-5x faster |
| Tick data | Aggregated into candles | Tick-by-tick processing |
| Slippage model | Simplified, % of volume | Any: AMM, limit orders |
| Look-ahead protection | None | Strict determinism, tests |
| Extensibility | Limited | Modular, any data source |
If you recognize your situation, request a consultation — we'll help find the optimal solution.
What's Included in Development?
| Phase | Deliverable | Duration (days) |
|---|---|---|
| Analysis and specification | Requirements document, API contracts | 3–5 |
| Architecture design | Event Model, DataFeed, Broker schemas | 3–5 |
| Core development | EventBus, Portfolio, SimulatedBroker code | 10–15 |
| Data integration | CSV/ClickHouse connectivity, custom feeds | 5–7 |
| Testing | Unit tests, integration scenarios, regression | 7–10 |
| Deployment and documentation | Repository, README, examples, CI/CD | 3–5 |
Experience shows: a custom framework pays for itself within a year under intensive use, and simulation speed increases by 3–5 times compared to Backtrader on multi-asset portfolios.
Timeline and Cost
Development timeline: from 2 to 12 weeks depending on complexity. Cost is calculated individually after strategy audit — contact us and get a preliminary estimate in 2 days. Typical projects range from $15,000 to $50,000, with ROI achieved within 6-12 months.
Common Mistakes in Self-Development
- Look-ahead bias — the most dangerous error, kills test reliability. Solved by strict determinism and tests.
- Ignoring slippage and commission — a strategy showing 50% annual return in ideal conditions may go negative in reality.
- Lack of unit testing — an error in margin calculation can cost millions. We require > 90% coverage.
- Poor event loop handling — deadlock or race condition in simulation leads to incorrect results. Our Event Bus is thread-safe and battle-tested for years.
We guarantee the framework will fully meet your requirements — with documentation, tests, and support during deployment. Order development today and get a strategy optimization consultation.







