Best Practices for Strategy Backtesting in Cryptocurrency Markets with DolphinDB

Backtesting cryptocurrency trading strategies is often challenging due to the market's high volatility, complexity, and a lack of mature tools. Many existing tools suffer from low performance and intricate implementation processes. DolphinDB addresses these issues by providing a powerful backtesting plugin that integrates distributed storage and computing, a multi-paradigm programming language, and a matching engine. It enables fast and efficient development and evaluation of crypto strategies.

The plugin supports multi-instrument trading, including spot, futures, perpetual contracts, and options, across various markets. This document introduces the usage of the DolphinDB backtesting plugin for cryptocurrencies and demonstrates practical case studies. All examples are based on tests performed with DolphinDB Server version 3.00.2.

1. Instructions

DolphinDB’s backtesting plugin supports various types of cryptocurrency market data, including snapshots, minute-level, and daily. This section explains how to backtest cryptocurrency strategies in DolphinDB, focusing on engine configuration, market data, and relevant interfaces.

1.1 Configurations

When creating a backtesting engine, you need to specify the engine parameters using the config parameter in the createBacktestEngine interface. The config parameter is a dictionary in which you can define various key-value pairs, such as the start and end dates, initial account balance, commission, market data type, and order matching mode, as outlined in Table 1-1.

First, the strategyGroup parameter must be set to "cryptocurrency" to conduct backtesting for cryptocurrency strategies. Unlike other asset classes, cryptocurrency backtesting supports simultaneous trading of spot, futures, and options instruments. Corresponding management accounts are created for each type, and you can specify their initial cash flow using the cash parameter.

In addition, the plugin supports trading of perpetual futures. You can configure the funding rate table for such contracts using the fundingRate parameter. See Table 1-2 for details.

Table 1-1: User Configurations

key Description Note
startDate start date DATE type, required (e.g., “2020.01.01”).
endDate end date DATE type, required (e.g., “2020.01.01”).
strategyGroup strategy group Must be "cryptocurrency"
cash initial cash flow (of dictionary form) for each account DOUBLE type, required.
  • spot: spot account
  • futures: futures and perpetual futures account
  • option: options account
commission commission DOUBLE type, required.
dataType the type of market data: 1, 3, 4
  • 1: snapshot
  • 3: minute-level data
  • 4: daily data
msgAsTable process the input market data as table or dictionary
  • true: table
  • false: dictionary
matchingMode matching mode: 1,2, 3 daily data:
  • 1: matching with the closing price.
  • 2: matching with the open price.
minute-level data:
  • 1: matching when market time > order time.
  • 2: matching with the closing price when market time = order time; matching unfilled orders with mode 1.
snapshot data:
  • matching with the order price.
orderBookMatchingRatio the proportion of an order that gets filled DOUBLE type, default is 1.0, valid range: 0 to 1.0.
matchingRatio matching ratio within price intervals DOUBLE type, valid range: 0 to 1.0. By default, it is equal to the orderBookMatchingRatio.
latency the latency from order submission to execution. DOUBLE type, in milliseconds.
fundingRate funding rate Accepts a table. For detailed schema, see Table 1-2.
benchmark benchmark instrument STRING or SYMBOL type, used in getReturnSummary (e.g., “BTCUSDT_0”)
isBacktestMode backtesting mode
  • true (default): backtesting
  • false: matching
enableIndicatorOptimize enable indicator calculation optimization
  • true: enable
  • false: disable
dataRetentionWindow data retention policy for indicator optimization STRING/INT type. Effective only when both enableIndicatorOptimize = true and isBacktestMode = true:
  • "ALL" (default): Retain all data.
  • "None": Retain no data.
  • Retain data by trading days, e.g., "20d" retains 20 trading days.
  • Retain data by row count, e.g., "20" retains the latest 20 rows per symbol.
addTimeColumnInIndicator add time column to indicator subscription results
  • true: add time column
  • false: omit time column
context strategy context structure A dictionary consisting of global strategy variables, for example:context=dict(STRING,ANY) context["buySignalRSI"]=70. context["buySignalRSI"]=30. config["context"]=context

Table 1-2 Schema of Configuration fundingRate

Field Data Type Description
symbol STRING or SYMBOL contract
settlementTime TIMESTAMP settlement time
lastFundingRate DECIMAL128(8) funding rate at settlement

Since cryptocurrency backtesting supports trading of futures and options contracts, the securityReference parameter must be specified when creating a backtest engine using Backtest::createBacktestEngine. This parameter provides the contract metadata required for simulation (see Table 1-3).

You are responsible for preparing a reference table containing the basic information of all contracts involved in the backtest. The table must conform to the required structure to be used as securityReference.

For code examples, refer to Section 2.2.3.

Table 1-3 Schema of Configuration securityReference

Field Data Type Description
symbol SYMBOL or STRING symbol
contractType INT contract type:
  • 0: spot
  • 1: delivery futures
  • 2: perpetual futures
  • 3: options
optType INT Options type:
  • 1: call
  • 2: put
strikePrice DECIMAL128(8) strike price
contractSize DECIMAL128(8) contract multiplier
marginRatio DECIMAL128(8) margin ratio
tradeUnit DECIMAL128(8) trade unit
priceUnit DECIMAL128(8) price unit
priceTick DECIMAL128(8) price tick
takerRate DECIMAL128(8) taker fee rate
makerRate DECIMAL128(8) maker fee rate
deliveryCommissionMode INT Specifies how the transaction fee is calculated when a trade is executed:
  • 1: makerRate (or takerRate) per lot
  • 2: Trade value × makerRate (or takerRate)
fundingSettlementMode INT Defines how the funding fee is settled between long and short positions for perpetual futures:
  • 1: lastFundingRate per lot
  • 2: Notional value × lastFundingRate
lastTradeTime TIMESTAMP Last trade time

Note:

  • The calculation of trading costs depends on parameters configured in Table 1-3 for each contract, such as margin ratio, fee rates, price units, etc. Since different contract may have different trading rules and fee standards, the required parameters must be configured individually for each contract.
  • For perpetual futures (contractType = 2), funding fees must be settled periodically while holding a position. The settlement method is defined by the fundingSettlementMode field, while the actual funding rate (e.g., lastFundingRate) should be retrieved from a separately configured funding rate table for perpetual futures.

1.2 Market Data

The structure of cryptocurrency market data slightly varies with data frequency. When using the backtesting engine, you must ensure that the input data strictly conforms to the required field names, field order, and data types. Failure to do so may result in backtest errors. If the existing data does not meet these requirements, you should perform data transformation during the import stage.

Currently, the backtesting engine supports three types of market data: snapshot, minute-level, and daily. This example uses minute-level data (dataType = 3), with its structure shown in Table 1-4.

Table 1-4 Schema of minute-level data

Field Data Type Description
symbol SYMBOL symbol
symbolSource SYMBOL exchange
tradeTime TIMESTAMP timestamp
tradingDay DATE Trading Day / Settlement Date(Used to determine backtest start/end dates and trading day switches)
open DECIMAL128(8) opening price
low DECIMAL128(8) lowest price
high DECIMAL128(8) highest price
close DECIMAL128(8) closing price
volume DECIMAL128(8) trading volume
amount DECIMAL128(8) trading amount
upLimitPrice DECIMAL128(8) limit up price
downLimitPrice DECIMAL128(8) limit down price
signal DOUBLE[] user-defined field
prevClosePrice DECIMAL128(8) previous closing price
settlementPrice DECIMAL128(8) settlement price
prevSettlementPrice DECIMAL128(8) previous settlement price
contractType INT contract type:
  • 0: Spot
  • 1: Delivery Futures
  • 2: Perpetual Futures
  • 3: Option

After the replay of historical data in backtesting is completed, you can append a message with the symbol set to "END" to indicate the end of the strategy backtest. For example:

messageTable = select top 1* from messageTable where tradeTime = max(tradeTime)
update messageTable set symbol = "END"
update messageTable set tradeTime = concatDateTime(tradeTime.date(), 16:00:00)
Backtest::appendQuotationMsg(engine, messageTable)

1.3 Backtesting Interfaces

You can build and run a backtest engine based on their custom strategies to retrieve backtest results such as daily positions, daily equity, summary of returns, trade details, and any user-defined logic outputs within the strategy. Some methods include additional parameters such as contractType and accountType to filter results by instrument type and account type.

This section focuses on the methods employed in the current example.

  • Backtest::createBacktestEngine: Initializes the backtest engine.
    • name: Name of the backtest engine.
    • config: Configuration settings for the engine. See Section 1.1 for details.
    • securityReference: The instrument reference table. See Section 1.1 for required fields.
    • initialize, beforeTrading, onTick/onBar, onSnapshot, onOrder, onTrade, afterTrading, and finalize: Event functions to be implemented based on the custom trading strategy.
    Backtest::createBacktestEngine(name, Config, [securityReference], initialize, 
    beforeTrading, onTick/onBar, onSnapshot, onOrder, onTrade, afterTrading, finalize)
  • Backtest::appendQuotationMsg: Appends market data to the engine and executes the strategy.
    • engine: The engine instance.
    • msg: The input market data. See Section 1.2 for required structure of minute-level data.
    Backtest::appendQuotationMsg(engine, msg)
  • Backtest::getPosition: Retrieves current position information.
    • engine: The engine instance.
    • symbol: Symbol code (optional; if not set, returns all symbols).
    • accountType: Account type ("spot", "futures", or "option").
    Backtest::getPosition(engine, symbol = "", accountType)
  • Backtest::submitOrder: Submits an order for the specified account.
    • engine: The engine instance.
    • msg: A tuple or table containing the order info (asset name, time to place, order type, price, quantity, direction).
    • label: optional. The custom label to distinguish orders.
    • orderType: optional. Algorithmic order type (default is 0).
    • accountType: optional. Account type ("spot", "futures", or "option").
     Backtest::submitOrder(engine, msg, label="",orderType = 0, accountType)

The methods above form the basic set for conducting backtests. The following result retrieval methods are typically used after the backtest ends to analyze strategy performance.

  • Backtest::getTradeDetails: Returns details about orders.
    Backtest::getTradeDetails(engine, accountType)
  • Backtest::getDailyPosition: Returns the position of a specific asset, including long/short position volume, average execution price, and trade volume.
    Backtest::getDailyPosition(engine, accountType)  
  • Backtest::getDailyTotalPortfolios: Returns the total equity of all portfolios, including available funds, daily return, and daily profit and loss.
    Backtest::getDailyTotalPortfolios(engine, accountType)
  • Backtest::getReturnSummary: Returns a summary of returns, including total return, annualized return, and annualized volatility.
    Backtest::getReturnSummary(engine, accountType)

2. Cryptocurrency Backtesting Case

Arbitrage strategies aim to profit from price discrepancies across different markets or instruments. In the cryptocurrency market, high volatility and differences in pricing mechanisms across exchanges create natural opportunities for arbitrage. Common strategies include cross-exchange arbitrage, contract arbitrage, statistical arbitrage, triangular arbitrage, and funding rate arbitrage.

These strategies typically involve hedging to lock in profits and reduce exposure to price movements, resulting in relatively low risk. However, traders must still consider market volatility, liquidity constraints, and transaction costs to ensure profitability and robustness.

This chapter demonstrates how to implement and backtest a funding rate arbitrage strategy using the DolphinDB backtesting engine.

2.1 Funding Rate Arbitrage

Funding rate arbitrage is a strategy unique to the cryptocurrency market, based on perpetual contracts. Key characteristics of perpetual contracts include:

  • No expiry date, allowing for continuous position holding.
  • Periodic funding rate payments (typically every 8 hours), where long positions pay shorts when the funding rate is positive, and shorts pay longs when it is negative.

Funding rates are set by exchanges to help maintain parity between the contract price and the spot price. This strategy exploits changes in the funding rate by dynamically adjusting positions in both perpetual contracts and the underlying spot asset to hedge against price fluctuations and capture funding payments. The core logic is:

  • Short perpetual + long spot: When the funding rate exceeds 0.03%, short the perpetual contract and buy the equivalent spot asset. Once the rate becomes negative, close the perpetual position and sell the spot holding.
  • Long perpetual + short spot: When the funding rate drops below -0.03%, go long the contract and sell the spot. When the rate turns positive, close the contract and buy back the spot.

Currently, DolphinDB does not support initializing positions at the beginning of a backtest. Therefore, this example implements only the short arbitrage strategy. Support for initial holdings will be added in future versions.

The following sections walk through implementing this strategy using the DolphinDB backtesting engine.

2.2 Strategy Implementation

2.2.1 Custom Trading Strategies

The backtesting system utilizes an event-driven design and provides a suite of event functions, including initialization function, daily callback, and snapshot data callback. You can implement custom strategy logic within the corresponding callback functions. This section demonstrates how each event function is implemented in this case.

  1. Using the initialize function, you can set up your strategy's key metrics and variables. In this example, userParam is a funding rate table. For detailed configuration, refer to the next section on engine parameter settings.

    Line 5 context["lastlastFunding"] stores the previous funding rate, which serves as a condition for closing positions.

    def initialize(mutable context, userParam){	
        // Initialization
    	print("initialize")
    	context["fundingRate"] = userParam
    	context["lastlastFunding"] = dict(SYMBOL,ANY)
    }
  2. In daily callback function beforeTrading, the trading date can be obtained via contextDict["tradeDate"], as shown in line 4. In this case, the current day's funding rate table is transformed into a nested dictionary d, which is then stored in the context for convenient access. A nested dictionary structure is used here, with the instrument symbol and settlement time as the keys.
    def beforeTrading(mutable context){
        // Daily callback
        // Get the current trading day
    	print ("beforeTrading: " + context["tradeDate"])
        // Retrieve current funding rates and store them as a dictionary
    	fundingRate = context["fundingRate"]
    	d = dict(STRING,ANY)
    	for (i in distinct(fundingRate.symbol))
    	{
    		temp = select * from fundingRate where symbol = i 
    			and date(settlementTime) = context["tradeDate"]
    		d[i] = dict(temp.settlementTime,temp.lastFundingRate, true)
    	}
    	context["dailyLastFundingPrice"] = d
    }
  3. In the market data callback function onBar, the parameter msg contains the latest minute-level market data passed from the backtesting engine, while the parameter indicator contains the subscribed indicators. Since this case does not involve indicator subscriptions, indicator will not be discussed in detail. The following code demonstrates the logic for opening and closing positions:
    • Since the msg dictionary contains market data for multiple instruments, a for(i in msg.keys()) {} loop is used to iterate through each symbol.
    • For each instrument, line 11 extracts the current funding rate—i.e., the fundingRate of the previous funding rate table. Then it checks whether the funding rate and current positions meet the criteria for opening or closing positions, and submits orders accordingly.
    def onBar(mutable context, msg, indicator = NULL){
        //...
    	dailyFundingRate = context["dailyLastFundingPrice"]
    	// Iterate over multiple instruments
    	for(i in msg.keys()){
    		istockSymbol = msg[i]["symbol"]  
    		istock = split(istockSymbol,"_")[0]
    		source = msg[i]["symbolSource"]
    		closePrice = msg[i]["close"]
    		// Get the funding rate for the current symbol based on the corresponding time interval
    		if(second(context["tradeTime"]) >= 16:00:00){
    			fundingRateTime = temporalAdd(datetime(context["tradeDate"]),16,"h")}
    		if(second(context["tradeTime"])>= 08:00:00 and second(context["tradeTime"]) 
    		< 16:00:00){
    			fundingRateTime = temporalAdd(datetime(context["tradeDate"]),8,"h")}
    		if(second(context["tradeTime"]) < 08:00:00){
    			fundingRateTime = datetime(context["tradeDate"])}
    		lastFundingPrice = dailyFundingRate[istock][fundingRateTime]
    		// Check the current position status: includes both spot and futures
    		spotPos = Backtest::getPosition(context["engine"], istock+"_0", "spot")
    		futurePos = Backtest::getPosition(context["engine"],istock+"_2","futures")
      //...
    		}
    }

In this example, context["fundingRate"] is the funding rate passed during initialization. You need to extract the rate for the corresponding symbol at the given time. Use Backtest::getPosition to retrieve current spot and futures positions.

Open position conditions are based on funding rate and position size, and orders are placed via Backtest::submitOrder.

// If funding rate > 0.03%, short perpetual contract and buy equivalent spot (max order size: 0.1) 		
if(spotPos.longPosition[0] <= 0.1 and futurePos.shortPosition[0] < 0.1 
    and lastFundingPrice > 0.0003){
	// Spot position
	if(istockSymbol == istock+"_0"){					
		Backtest::submitOrder(context["engine"], (istockSymbol, source, 
			context["tradeTime"], 5, closePrice,lowerLimit, upperLimit, qty,1, 
			slippage, 1, expireTime ), "buyopen_spot", 0, "spot")	 	 
		}
	// Futures position
	if(istockSymbol == istock + "_2"){		
		Backtest::submitOrder(context["engine"], (istockSymbol, source, 
			context["tradeTime"], 5, closePrice,lowerLimit, upperLimit, qty, 2, 
			slippage, 1, expireTime ), "sellopen_contract", 0, "futures")		
		}
	}
context["lastlastFunding"][istock] = lastFundingPrice[0]
}

For closing positions, check if the previous funding rate (context["lastlastFunding"]) was positive and the current rate is negative, and whether the position size is greater than 0.01.

// Close positions when funding rate turns negative	   
 if(Pos_spot.longPosition[0] > 0.1  and Pos_futures.shortPosition[0] > 0.1 
    and context["lastlastFunding"][istock] >= 0 and lastFundingPrice < 0){
    // Close spot
    if(istockSymbol == istock+"_0"){
			Backtest::submitOrder(context["engine"], (istockSymbol, source, 
				context["tradeTime"], 5, closePrice,lowerLimit, upperLimit, qty,3, 
				slippage, 1,expireTime ), "sellclose_spot", 0, "spot")  
			        }
    // Close futures
    if(istockSymbol == istock+"_2"){
			Backtest::submitOrder(context["engine"], (istockSymbol, source, 
				context["tradeTime"], 5, closePrice,lowerLimit, upperLimit, qty, 4, 
				slippage, 1,expireTime ), "buyclose_contract", 0, "futures")
			        }
	   }

Note: In submitOrder, the parameters upperLimit, lowerLimit, slippage, qty, and expireTime refer to take-profit price, stop-loss price, slippage, order quantity, and order expiration time, respectively. You can customize these based on your strategy.

In addition to market-driven strategies, the backtesting engine also supports handling order status updates, trade execution events, end-of-day accounting, and other finalization logic.

2.2.2 Parameter Configuration

The backtest engine allows configuration of parameters such as start and end dates, initial capital, market type, order delay, and funding rates for perpetual contracts. You can adjust these settings to simulate different market conditions and evaluate strategy performance.

In this example, the strategy is for cryptocurrency trading, so the strategyType parameter should be set to "cryptocurrency". This ensures the engine applies the correct rules and logic for digital asset markets.

Example code for initializing parameters:

startDate = 2024.01.01
endDate = 2024.01.15
config = dict(STRING, ANY) 
config["startDate"] = startDate  
config["endDate"] = endDate  
config["strategyGroup"] = "cryptocurrency"  // Strategy type: cryptocurrency
cash = dict(STRING, DOUBLE)  // Initial account capital
cash["spot"] = 1000000.
cash["futures"] = 1000000.
cash["option"] = 1000000.
config["cash"] = cash
config["dataType"] = 3     // Market data type: minute-level
// Funding rate: passed to the engine via userParam
config["fundingRate"] = select  symbol, settlementTime, decimal128(lastFundingRate,8)
 as lastFundingRate from CryptoFundingRate where  date(settlementTime) >= startDate 
 and date(settlementTime) <= endDate  order by settlementTime
userParam = table(1:0,[`symbol,`settlementTime,`lastFundingRate],[SYMBOL,TIMESTAMP,DECIMAL128(8)])
userParam = config["fundingRate"]

Note: The config["fundingRate"] parameter specifies the funding rate for cryptocurrency perpetual contracts. It can be passed into event functions using userParam. In this case, make sure to add userParam as an argument when defining and registering event functions, e.g., def initialize(mutable contextDict, userParam) or initialize{, userParam}.

2.2.3 Creating and Executing the Backtest

When creating the backtestint engine, select event functions based on data frequency and strategy needs. In this case, only onBar is needed for minute-level data, and onSnapshot is skipped by leaving its position empty (using a comma placeholder).

// Create a backtest engine
strategyName = "Cryptocurrency"
try{Backtest::dropBacktestEngine(strategyName)}catch(ex){print ex}
engine = Backtest::createBacktestEngine(strategyName, config, securityReference, 
initialize{,userParam}, beforeTrading, onBar,, onOrder, onTrade,, finalize)
go

For cryptocurrency backtests, set the securityReference parameter based on contract info. Use SQL (e.g., update, select) to ensure the schema matches requirements in Section 1.1, or load a prepared table with loadText.

// Security reference table
securityReference = select last(contractType) as contractType 
from testData group by symbol 
update securityReference set optType = 1
update securityReference set strikePrice = decimal128(0, 8)
update securityReference set contractSize = decimal128(100.,8)
update securityReference set marginRatio = decimal128(0.2,8)
update securityReference set tradeUnit = decimal128(0.2,8)
update securityReference set priceUnit = decimal128(0.,8)
update securityReference set priceTick = decimal128(0.,8)
update securityReference set takerRate = decimal128(0.,8)
update securityReference set makerRate = decimal128(0.,8)
update securityReference set deliveryCommissionMode = iif(contractType!=2,1,2)    
update securityReference set fundingSettlementMode = iif(contractType==2,1,2)     
update securityReference set lastTradeTime = timestamp()   // Last settlement time

Once you’ve set up the engine, you can use the appendQuotationMsg method to append market data to the engine and execute the backtesting. The testData variable contains minute-level market data. Convert it if needed to match the required format (see Section 1.2).

// Market data: extracted and converted to match required structure and types
cryptoMinData = loadText("root/data/Crypto1minData.csv") 
testData = select symbol + "_" + string(contractType) as symbol,symbolSource,
tradeTime,tradingDay,decimal128(open,8) as open,decimal128(low,8) as low, 
decimal128(high,8)as high,decimal128(close,8)as close,decimal128(volume,8)as volume,
decimal128(amount,8) as amount, decimal128(upLimitPrice,8) as upLimitPrice,
decimal128(downLimitPrice,8) as downLimitPrice,signal,decimal128(prevClosePrice,8)as
prevClosePrice,decimal128(settlementPrice,8) as settlementPrice,
decimal128(prevSettlementPrice,8) as prevSettlementPrice,contractType 
from Crypto1minData  where  date(tradeTime) >= startDate and date(tradeTime) <= 
endDate order by tradeTime
//......
Backtest::appendQuotationMsg(engine,testData)

2.2.4 Checking results

The Backtest plugin offers a list of methods to obtain the corresponding results, including:

  • getPosition/getDailyPosition: Returns the position of a specific asset.
  • getTradeDetails: Returns details about orders.
  • getAvailableCash: Returns the account's available cash.
  • getTodayPnl: Returns the profit and loss for the current day.
  • getTotalPortfolios/getDailyTotalPortfolios: Returns the total equity of all portfolios.
  • getReturnSummary: Returns a summary of returns.
  • getContextDict: Returns the global variables.

For cryptocurrency strategies, some methods support specifying the account type (e.g., spot, futures, option). Below are examples of commonly used result queries. The field “direction” indicates order direction (1 means buy open).

// Retrieve trade details; returns all trades if accountType is not set
Backtest::getTradeDetails(engine, 'spot') 
Figure 1. Figure 2-1: Spot Trade Details

Use Backtest::getDailyPosition to get daily positions by account type. The futures position data in this example is shown in Figure 2-2. Fields like “lastDayLongPosition”, “shortPosition”, and “shortPositionAvgPrice” represent the previous day's long position, current short position, and average short price, respectively.

// Retrieve daily positions; accountType can be specified
Backtest::getDailyPosition(engine, 'futures')    
Figure 2. Figure 2-2: Daily Futures Positions

Use Backtest::getReturnSummary to view the overall strategy performance. If accountType is specified, results are limited to that account; otherwise, returns cover all accounts. Figure 2-3 shows the return summary. Key fields include “totalReturn” and “annualReturn”.

// Return summary; specify accountType to filter, or omit to show all accounts
Backtest::getReturnSummary(engine)
Figure 3. Figure 2-3: Account Return Summay

3. Advantages

DolphinDB's backtesting plugin integrates distributed storage and computing, a multi-paradigm programming language, and a matching engine simulator, offering several advantages for cryptocurrency strategy development.

  • It provides a standardized framework with multiple event functions and a comprehensive set of engine APIs. Users can simply implement event functions, call relevant APIs, and configure parameters to build and run strategies. After the backtest is completed, users can retrieve the results via APIs for comprehensive performance analysis.
  • DolphinDB includes rich indicator libraries like mytt and ta. It also supports custom factor development using powerful built-in functions such as sliding windows and aggregations, greatly improving both performance and development efficiency.
  • The plugin supports multi-account trading across various asset types, including spot, futures, perpetual contracts, and options. Users can run strategies for multiple accounts and analyze the results separately.
  • For high-frequency strategies, the built-in matching engine accurately simulates real trading logic, helping to better estimate real-world performance.

These advantages position DolphinDB as a robust and practical choice for complex cryptocurrency backtesting, outperforming tools like Backtrader and QuantConnect in areas such as high-precision computation and multi-account strategy support.

4. Conclusion

DolphinDB's backtesting plugin provides a standardized framework, rich APIs, and high-performance computing, enabling fast and flexible cryptocurrency strategy development. This document demonstrates how to configure the engine, define custom strategy functions, and create a backtesting engine. Through a funding rate arbitrage case, we demonstrate how to implement and validate a complete crypto strategy using DolphinDB.