Stop Backtesting in Theory, Simulate the Real Crypto Market with DolphinDB

DolphinDB
2026-05-28

In the fast-paced world of cryptocurrency trading, the difference between profit and loss often comes down to milliseconds. Quantitative traders need robust infrastructure to test strategies before risking real capital—but traditional backtesting frameworks struggle with the unique challenges of crypto markets: massive tick data volumes, complex order matching logic, and the need for real-time simulation.

This is where having the right technical foundation matters. At DolphinDB, we've built an integrated backtesting and simulation solution specifically designed for mid- to high-frequency cryptocurrency trading. By combining our high-performance time-series database with a sophisticated order matching engine, we're helping traders move from strategy conception to live deployment faster and more confidently.

In this guide, we'll walk you through the complete workflow—from setting up your first backtest to running real-time simulations with full code versioning and team collaboration features.

The Challenge: Why Crypto Backtesting Is Different

Before deploying any quantitative strategy to live trading, extensive validation is essential. But cryptocurrency backtesting presents two critical challenges that traditional equity backtesting frameworks weren't built to handle:

  1. Massive Data Storage and Processing

High-frequency snapshot data in crypto markets generates enormous data volumes. A single trading pair can produce millions of records per day. This demands exceptional framework-level performance for both data processing and computation.

  1. Complex Matching Logic

Order execution isn't as simple as using the latest price. Realistic backtesting must account for order price, traded volume, market volatility, and liquidity conditions—requiring a sophisticated order matching simulator.

The DolphinDB Solution: End-to-End Backtesting and Simulation

Leveraging our distributed architecture and low-latency streaming engine, DolphinDB integrates a backtesting engine with an order matching simulator to deliver a complete solution. Here's what it includes:

  • Market data replay and processing

  • Strategy development and code management

  • Result visualization and analytics

  • Strategy permission management for teams

The platform supports both snapshot and minute-level cryptocurrency data, enabling individuals and quantitative teams to develop strategies efficiently with minimal code and a gentle learning curve. Beyond native DLang, strategies can be written in Python and C++, offering flexibility without sacrificing performance.

1. Understanding the Cryptocurrency Backtesting Engine

The cryptocurrency backtesting engine is delivered as a plugin with a straightforward logical flow:

  1. The engine replays market data streams in chronological order and dispatches them to the order matching simulator and market data callback functions.

  2. Market data callback functions process strategy logic and submit orders.

  3. The engine performs risk control management.

  4. Orders that pass risk control are sent to the order matching simulator for execution.

  5. The engine tracks positions and funds in real time, and returns strategy returns and trade details after backtesting completes.

The engine supports both snapshot and minute-level data for spot and futures across multiple exchanges. It can manage multiple accounts and contract types within a single engine instance.

One of the biggest pain points in backtesting is data preparation. Market data must conform to strict schemas, often requiring substantial cleansing and transformation. Since strategy researchers should focus on designing and optimizing strategy logic—not wrestling with data pipelines—we've streamlined this entirely.

Our solution provides not just raw exchange data, but fully processed and replay-ready snapshot and minute-level market data. This means you can focus exclusively on your custom strategy functions and configuration to quickly complete backtesting.

2. Strategy Development and Code Management

All market data access, backtest execution, trade simulation, and code management functions are encapsulated in the CryptocurrencySolution module. Based on the cryptocurrency data environment built in Engineering Always-on Market Data Infrastructure for Crypto Trading, you can directly use this module to perform strategy backtesting.

The sub-modules are described below:

Sub-moduleCatagoryDescription
CryptocurrencySolution::utilsCommon functionsIncludes common functions such as engine deletion and management, funding rate retrieval, base information tables, OHLC data downsampling, backtest and simulation strategy retrieval, strategy log retrieval, and database/stream table status checks.
CryptocurrencySolution::settingUnified settingsIncludes unified strategy configuration, GitLab address configuration, module lists, and database/table metadata.
CryptocurrencySolution::runBacktestBacktestingIncludes market data retrieval and processing functions, and backtest execution functions.
CryptocurrencySolution::simulatedTradingSimulationIncludes market data retrieval and processing functions, and simulation start/stop function.
CryptocurrencySolution::manageScriptsCode storageIncludes strategy upload, retrieval, and deletion functions, backtest result storage and retrieval functions, and backtest/simulation code submission and storage function.

First, obtain the home directory using getHomeDir(), place the attached module folder in the corresponding path, and load it using use:

use CryptocurrencySolution::manageScripts
go

To meet strategy storage and management requirements, this solution integrates with GitLab. When submitting a strategy for backtesting or simulation, the system automatically uploads the strategy code to a specified Git repository, enabling unified management, version tracking, and rapid retrieval. For details, see Section 4.

If strategy code storage is not required, you can use the following modules for backtesting or simulation only:

// Backtesting
use CryptocurrencySolution::runBacktest
go
// Simulation
use CryptocurrencySolution::simulatedTrading
go

2.1 Writing Cryptocurrency Strategies

The backtesting engine adopts an event-driven mechanism and provides the following event functions. You can customize callbacks for strategy initialization, daily pre-market processing, snapshot and OHLC market data, and order and trade reports.

Event FunctionDescription
initialize(mutable context)Strategy initialization function: triggered once. The parameter context represents the logical context. In this function, you can use context to initialize global variables or subscribe to metric computation.
beforeTrading(mutable context)Callback executed during the daily session rollover; triggered once when the trading day switches.
onSnapshot(mutable context, msg)Snapshot market data callback.
onBar(mutable context, msg)Low- to mid-frequency market data callback.
onOrder(mutable context, orders)Order report callback, triggered whenever an order’s status changes.
onTrade(mutable context, trades)Trade execution callback, triggered when a trade occurs.
finalize(mutable context)Triggered once before strategy termination.

Note:

  • If GitLab-based strategy code management is required, the strategy must be declared using module, and the module name must match strategyName.

  • Required modules used in callbacks must be listed in getUsedModuleName() in CryptocurrencySolution::setting. Otherwise, strategy code will fail to be uploaded.

module testMinStrategy   //Declare the strategy module when enabling code management
def initialize(mutable context){
}
def beforeTrading(mutable context){
}
def onBar(mutable context,msg, indicator){
}
def onSnapshot( mutable context,msg, indicator){
}
def onOrder( mutable context,orders){
}
def onTrade(mutable context,trades){
}
def finalize (mutable context){
}
strategyName = "testMinStrategy"

2.2 Cryptocurrency Strategy Configuration

The userConfig strategy configuration must include the strategy type, backtest start and end dates, market data type, initial capital, and the cryptocurrency asset to be backtested. The CryptocurrencySolution::setting module provides default strategy configuration, which can be obtained using the following function:

startDate = 2025.08.24
endDate = 2025.08.25
dataType = 3 // Minute-level market data; set to 1 for snapshot data
userConfig = CryptocurrencySolution::setting::getUnifiedConfig(startDate, endDate, dataType)

An example of the userConfig is shown below:

sym = ["BTCUSDT"]
userConfig = dict(STRING,ANY)
userConfig["startDate"] = 2025.08.24
userConfig["endDate"] = 2025.08.25
userConfig["strategyGroup"] = "cryptocurrency"
cash = dict(STRING,DOUBLE)
cash["spot"] = 1000000.
cash["futures"] = 1000000.
cash["option"] = 1000000.
userConfig["cash"] = cash
userConfig["dataType"] = 3    // 1: snapshot; 3: minute-level
userConfig["Universe"] = sym  // Symbol for backtesting (required)
p = dict(STRING,ANY)
p["Universe"] = userConfig["Universe"] + "_futures"
p["log"] = table(10000:0,[`tradeDate,`time,`info],[DATE,TIMESTAMP,STRING]) // log table for debugging
userConfig["context"] = p    // Pass the asset universe

To backtest historical market data, you can customize the start and end dates in userConfig. This module automatically loads the default strategy configuration and allows user-provided userConfig values to override the defaults. By default, Binance data is used, and the backtest period is the most recent 10 days.

userConfig = dict(STRING,ANY)
userConfig["startDate"] = 2025.08.24
userConfig["endDate"] = 2025.08.25
// Example: backtest an asset (used to define the asset universe)
p = dict(STRING,ANY)
p["Universe"] = ["XRPUSDT_futures"]
userConfig["context"] = p
strategyType = 0
engine, straname_ =
    CryptocurrencySolution::manageScripts::runCryptoAndUploadToGit(
        strategyName, eventCallbacks, strategyType, userConfig
    )

To downsample OHLC data, you can use the built-in downsampling function and specify the barMinutes parameter:

strategyType = 0
engine, straname_ =
    CryptocurrencySolution::manageScripts::runCryptoAndUploadToGit(
        strategyName, eventCallbacks, strategyType, , barMinutes = 1
    )

To use market data from the OKX exchange, specify exchange = "OKX". Otherwise, it defaults to "Binance":

engine, straname_ =
    CryptocurrencySolution::manageScripts::runCryptoAndUploadToGit(
        strategyName, eventCallbacks, strategyType, exchange = "OKX"
    )

2.3 Strategy Backtest Execution and Result Retrieval

After setting the strategyName , eventCallbacks , and strategyType parameters, call runCryptoAndUploadToGit to execute the backtest.

strategyName = "testMinStrategy"
eventCallbacks = {
    "initialize": initialize,
    "beforeTrading": beforeTrading,
    "onBar": onBar,
    "onOrder": onOrder,
    "onTrade": onTrade,
    "finalize": finalize
}
// Execute backtest and upload strategy files to GitLab
strategyType = 0
engine, straname_ =
    CryptocurrencySolution::manageScripts::runCryptoAndUploadToGit(
        strategyName, eventCallbacks, strategyType
    )
  • During backtest execution, the strategy files are automatically uploaded to the specified GitLab repository, and the corresponding commit ID is stored in the gitstrategy table of the database dfs://CryptocurrencyStrategy.

  • If strategy code management is not required, you can use runCryptoBacktest from the CryptocurrencySolution::runBacktest module. This function automatically loads default strategy configuration and allows you to override it with a custom userConfig. By default, it performs backtesting on Binance data for the most recent 10 days.

// userConfig = dict(STRING,ANY)
// userConfig["startDate"] = 2025.11.10
// userConfig["endDate"] = 2025.11.20
engine, straname_ =
    CryptocurrencySolution::runBacktest::runCryptoBacktest(
        strategyName, eventCallbacks, userConfig
    )

Retrieve backtest results

After the backtest completes, you can retrieve daily positions, daily equity, return summary, trade details, and strategy configurations using the following methods.

// Trade details
tradeDetails = Backtest::getTradeDetails(engine, "futures")
// Unfilled orders
tradeDetails[tradeDetails.orderStatus == -3]
// Current open (unfilled) orders
Backtest::getOpenOrders(engine, "futures")
// Daily positions
Backtest::getDailyPosition(engine, "futures")
// Overall backtest summary
Backtest::getReturnSummary(engine)
  • After the backtest completes, the strategy return summary and configuration are stored in dfs://CryptocurrencyStrategy/backtestStrategy for comparative analysis.

  • Visualization of backtest results is described in Section 3.

2.4 Submitting Simulated Trading

After real-time market data is ingested into stream tables, set strategyType = 1 to enable simulated trading. The strategy name will automatically be suffixed with SimulatedTrading, and the strategy code will be managed and stored.

You can use the overwrite parameter to control whether strategies with the same name are automatically deleted (defaults to false). By default, Binance data is used and simulation starts from the current day.

strategyType = 1
engine, straname_ =
    CryptocurrencySolution::manageScripts::runCryptoAndUploadToGit(
        strategyName, eventCallbacks, strategyType,
        userConfig = NULL, overwrite = false
    )

After simulation starts, trade details, real-time positions, and equity are written in real time to the corresponding simulation tables, for example:

  • strategyName + "_tradeDetails"

  • strategyName + "_position"

If strategy code management is not required, you can run simulated trading using the submitCryptoSimulatedTrading function in the CryptocurrencySolution::simulatedTrading module. This function automatically loads the default strategy configuration and allows you to override it with a custom userConfig. To run a simulation based on an existing backtest strategy, use the submitCryptoSimulatedTradingByStrategyName function, which submits the backtest strategy (strategyName_) in simulation mode.

// Directly submit simulated trading
engine, straname_ = submitCryptoSimulatedTrading(strategyName, eventCallbacks)
// Submit simulated trading based on an existing backtest strategy
engine, straname_ = submitCryptoSimulatedTradingByStrategyName(strategyName_, overwrite = false)
  • Simulation result dashboards are described in Section 3.

  • If you need to run simulated trading on downsampled OHLC data, use the time-series engine to process the original stream tables. For details, see klineMergeScript.dos in the Appendix.

2.5 Retrieving Strategy Logs

To simplify debugging, you can write logs inside strategy callback functions:

def beforeTrading(mutable context){
    // Write logs to the engine-maintained log table
    context.log.tableInsert(context["tradeDate"], now(), "beforeTrading")
}

Use getStrategyLog to retrieve strategy logs. If you define a custom userConfig , you must manually add log settings.

CryptocurrencySolution::utils::getStrategyLog(straname_)

Sample strategy logs:

2.6 Deleting Strategies

To avoid excessive engine creation, each user is limited to a maximum of 5 strategy engines. Unneeded engines can be removed using dropMyBacktestEngine. For simulated strategies, the corresponding result tables are also deleted. You can only delete strategies created by yourself.

use CryptocurrencySolution::utils
straname_ = 'admin_testMinStrategy'
dropMyBacktestEngine(straname_)

2.7 Strategy Code Management

To meet requirements for strategy code storage and versioning, we integrate backend modules with a GitLab repository. When a backtest or simulation is submitted, the backend automatically uploads the strategy event callback functions to a user-defined GitLab repository and records version information in the corresponding database tables. All GitLab-related strategy management functions are encapsulated in the CryptocurrencySolution::manageScripts module.

First, you must create a GitLab account and obtain a GitLab Personal Access Token for authentication. See Personal access tokens | GitLab Docs for details. When using strategy code management, you can either pass gitInfo or configure it globally in the CryptocurrencySolution::setting module. Example:

gitInfo = dict(STRING,ANY)
gitInfo[`gitLabSite] = "https://dolphindb.net"   // GitLab URL
gitInfo[`repoId] = 620                          // Project ID
gitInfo[`repoBranch] = "main"                   // Branch name
gitInfo[`privateToken] = "zQx-BzyJrHxd_sbnEFNR"  // Access token

Below are the main functions provided by this module, including uploading, retrieving, and deleting strategies.

Upload strategy files

def uploadOrupdateToGit(
    strategyName, strategyType, eventCallbacks,
    gitInfo = NULL, commitMsg = NULL, timeout = 10000
)

Parameter description

ParameterType/FormDescriptionRequired
strategyNameSYMBOLStrategy nameYes
strategyTypeINTStrategy type (0: backtest, 1: simulation)Yes
eventCallbacksDICTEvent callback functionsYes
gitInfoDICTGitLab configuration; an example is displayed at the start of the section.No
commitMsgSTRINGCommit mesaageNo
timeoutINTRequest timeoutNo

As described in Section 2, you can run backtests or simulations via runCryptoAndUploadToGit, which automatically uploads strategy code. Strategy versions (both new and updated) are recorded in dfs://CryptocurrencyStrategy/gitstrategy. You can retrieve specific versions using the stored commit IDs.

Retrieve strategy files

def getStrategyFromGit(
    strategyName, commitId = NULL, gitInfo = NULL, timeout = 10000
)

Parameter description

ParameterType/FormDescriptionRequired
strategyNameSYMBOLStrategy nameYes
commitIdSTRINGID of the commit that uploads the strategyNo
gitInfoDICTGitLab configuration; an example is displayed at the start of the section.No
timeoutINTRequest timeoutNo

By default, this function retrieves the latest version of the strategy. To fetch a specific version, specify commitId based on the records in dfs://CryptocurrencyStrategy/gitstrategy.

Delete strategy files

def deleteStrategyFromGit(
    strategyName, gitInfo = NULL, timeout = 10000
)

Parameter description

ParameterType/FormDescriptionRequired
strategyNameSYMBOLStrategy nameYes
gitInfoDICTGitLab configurationNo
timeoutINTRequest timeoutNo

This function deletes the corresponding strategy file from GitLab. Use with caution. You can only delete strategies uploaded by yourself.

Submit backtests or simulations with code upload

def runCryptoAndUploadToGit(
    strategyName, eventCallbacks, strategyType, userConfig = NULL,
    barMinutes = 1, overwrite = false, gitInfo = NULL,
    commitMsg = NULL, timeout = 10000
)

Parameter description

ParameterType/FormDescriptionRequired
strategyNameSYMBOLStrategy nameYes
eventCallbacksDICTEvent callback functionsYes
strategyTypeINTStrategy type (0: backtest, 1: simulation)Yes
userConfigDICTStrategy configuration (Only start/end time can be modified; defaults are defined in setting)No
exchangeSTRINGExchange providing the source data (default: "Binance").No
barMinutesINTFrequency in minutes, effective for OHLC data only.No
overwriteBOOLWhether to delete existing simulation strategies with the same name (default: false).No
gitInfoDICTGitLab configurationNo
commitMsgSTRINGCommit mesaageNo
timeoutINTRequest timeoutNo

This function wraps backtest/simulation submission and strategy code upload, using default strategy configuration. You only need to define strategyName , eventCallbacks , and strategyType to quickly deploy a cryptocurrency strategy. For details, see Section 2.3.

3. Strategy Result Visualization

To help you analyze backtesting and simulated trading results more intuitively, we provide separate dashboards for backtesting and simulation. These dashboards enable visual strategy management. You only need to import the dashboard files included in the Appendix—no additional configuration is required.

3.1 Backtesting Strategy Visualization

The backtesting dashboard displays all backtesting strategies and their results. It includes the following content:

  • Strategy list

Shows the strategy names, engine status, error messages, and related information.

  • Backtesting results

Select a strategy name and click Query to retrieve the backtesting results.

  • Submit strategy for simulation

You can submit strategies that have been backtested for simulated trading. The simulation strategies will appear in the simulation dashboard.

  • Delete backtesting strategies

You can delete unneeded backtesting strategies.

3.2 Simulated Trading Strategy Visualization

The simulation dashboard displays all simulated trading strategies and their results. Its structure is the same as the backtesting dashboard. The page refreshes once per second to display real-time strategy results.

  • Simulation strategy list

  • Real-time trades and positions

  • Delete simulation strategies

You can delete unneeded simulation strategies.

  • Stop simulated trading

Simulation strategies that are no longer needed can be stopped. After stopping, the strategy will appear in the backtesting dashboard.

4. Strategy Permission Management

To support strategy isolation within quantitative teams, we implement user-level permission isolation for both backtesting and simulation strategies. Each user can only view the backtesting and simulation results they created, and strategy code is stored in user-defined GitLab repositories.

To use the cryptocurrency data environment for backtesting or simulation, contact the admin to create a user account with the required read permissions on the relevant databases and tables. Then, you can log in as the created account and import the dashboard files to view backtesting and simulation strategies. A permission granting script is shown below:

// Executed by admin
login("admin","DolphinDB123456")
// Create a new user
createUser("user1","123456",,false)
// Grant database and table permissions
grant(`user1, DB_READ, "dfs://CryptocurrencyDay")
grant(`user1, DB_INSERT,"dfs://CryptocurrencyStrategy")
grant(`user1, TABLE_READ, "P1-node1:Cryptocurrency_depthTradeMerge")
// Function view permissions
grant("user1", VIEW_EXEC, "CryptocurrencySolution::utils::getSimulatedTradingEngine")
grant("user1", VIEW_EXEC, "CryptocurrencySolution::utils::getAllBacktestEngine")
// Dashboard permissions
dashboard_grant_functionviews(`user1, NULL, false)
// Grant permissions to group member bk
grant(`bk, VIEW_EXEC, objs="CryptocurrencySolution::utils::getSimulatedTradingEngine")
grant(`bk, DB_INSERT, "dfs://CryptocurrencyStrategy")   // Permission on database storing backtesting results
grant(`bk, VIEW_OWNER)   // Strategy storage requires function view permission

Note: If you want to share strategy backtesting results, use the dashboard sharing feature. Shared dashboards are view-only.

For centralized management, the following functions are restricted to the admin user:

  • Unsubscribe all minute-level or snapshot stream tables.
CryptocurrencySolution::utils::unsubscribeTableByAdmin(tbName)
  • Retrieve return comparisons for all simulated strategies.
CryptocurrencySolution::utils::getReturnComparision(startDate, endDate)
  • Rebuild the database for strategy IDs and backtesting results. (Write permissions must be re-granted to the specific user after rebuilding.
CryptocurrencySolution::utils::createdatabase()

Conclusion: From Concept to Live Trading, Faster

Building a successful quantitative crypto trading operation requires more than just good ideas—it demands robust infrastructure that can handle massive data volumes, complex execution logic, and seamless collaboration across teams.

With DolphinDB's integrated backtesting and simulation solution, you get:

  • Performance at scale : Handle millions of ticks per day without breaking a sweat

  • Realistic execution : Sophisticated order matching that mirrors real market conditions

  • Rapid iteration : From strategy idea to validated backtest in minutes, not days

  • Team collaboration : Built-in version control and permission management

  • Seamless deployment : One-parameter switch from backtest to live simulation

Whether you're an individual trader testing your first momentum strategy or a quantitative fund managing dozens of algorithms across multiple exchanges, this platform provides the foundation you need to move confidently from concept to live trading.

Ready to start building? Check out our documentation at http://www.dolphindb.com/ or reach out to our team at info@dolphindb.com to get started.