Backtesting with historical trade data is the process of replaying a strategy's decisions against past prices, fills and trading costs. For the final version, add the test period, such as ****, and follow the seven stages below.
First, determine whether you have a completed trade log or historical market data. Clean the records, recreate entries and exits using plausible prices and costs, build a chronological equity curve, then test the strategy on data that was not used during development.
A completed trade log can show how recorded trades performed. It cannot prove that the strategy would have found every valid opportunity.
Backtesting Historical Trade Data at a Glance
| Step | What to do | Main risk |
|---|---|---|
| 1. Identify the data | Separate trade logs from market, tick or quote data | Testing the wrong question |
| 2. Clean the records | Check timestamps, duplicates, missing values and prices | False performance |
| 3. Recreate execution | Include spread, commission, slippage, latency and partial fills | Overstated returns |
| 4. Calculate trade results | Compute gross and net profit for each trade | Incorrect long or short logic |
| 5. Build equity curves | Track balance, drawdown and exposure over time | Missing open-position risk |
| 6. Validate the strategy | Use chronological development, validation and test periods | Overfitting |
| 7. Test stability | Change costs, markets and market conditions | Fragile results |
What Type of Historical Trade Data Do You Have?
The data type determines what your backtest can answer.
| Data type | What it can test | What it cannot reliably test |
|---|---|---|
| Completed trade log | Profitability, risk, win rate, drawdown and trade management | Whether the strategy found every valid entry |
| OHLCV candle data | Signal generation and approximate execution | Exact intrabar fills and true bid-ask execution |
| Historical tick and quote data | Trade timing, spread, stop orders and detailed execution | Results when the data has gaps or lacks market depth |
| Order book data | Queue position, liquidity and market impact | Long-term performance without enough history |
Can You Backtest a Strategy Using Only a Trade Log?
You can evaluate the completed trades, but a trade log alone cannot fully test signal generation.
A trade log usually includes only trades that were taken. It does not show:
- Signals that were ignored
- Trades that were rejected
- Market conditions when no trade occurred
- Available bid and ask prices
- Intraday losses while positions were open
- Orders that would have received partial fills
- Alternative entries and exits
A trade log is useful for performance analysis, execution review and trade-management testing. It does not prove that the strategy would have generated those trades without hindsight.
What Columns Should Historical Trade Data Contain?
A basic completed-trade file should contain:
trade_id
symbol
side
entry_time
exit_time
entry_price
exit_price
quantity
fees
A more detailed file may also include:
entry_bid
entry_ask
exit_bid
exit_ask
entry_order_type
exit_order_type
entry_slippage
exit_slippage
commission_currency
leverage
margin_used
For multi-leg trades, add a shared position_id or trade_group_id. This helps you group:
- Scale-in orders
- Scale-out orders
- Pairs trades
- Options spreads
- Futures calendar spreads
- Hedged positions
Use one timezone throughout the dataset, preferably UTC. Store enough timestamp precision to preserve the order in which events occurred.
How to Clean Historical Trade Data Before Backtesting
Data cleaning is part of the backtest. Do not calculate performance until the records pass basic checks.
Check Timestamps
Confirm that:
- Entry time occurs before exit time
- Trades are sorted in time order
- Timestamps use the correct timezone
- Daylight-saving changes have not shifted executions
- Multiple trades with the same timestamp have a defined order
A timezone error can put a trade in the wrong session. That can change the opening price, overnight charges or exposure to a news event.
Check Prices and Quantities
Look for:
- Zero or negative prices
- Zero quantities
- Missing entries or exits
- Incorrect decimal places
- Unit mismatches between contracts, shares and currency
- Duplicate trade IDs
- Impossible profit and loss values
For futures, include the contract multiplier. A one-point move in an E-mini S&P 500 futures contract has a different cash value from a one-point move in a single share.
Adjust for Corporate Actions
For stocks, establish whether prices account for:
- Stock splits
- Reverse splits
- Dividends
- Rights issues
- Delistings
Trade records and price data must use compatible adjustment rules. Combining adjusted entry prices with unadjusted exits can create artificial gains or losses.
Preserve Delisted Instruments
If you are testing a stock universe, keep companies that later merged, failed or were delisted. Removing them creates survivorship bias because the test contains only companies that remained in the dataset.
How to Calculate Profit and Loss From Each Trade
For a long position:
Gross P&L = (Exit Price - Entry Price) × Quantity
For a short position:
Gross P&L = (Entry Price - Exit Price) × Quantity
A single formula can handle both directions:
Gross P&L = (Exit Price - Entry Price) × Quantity × Direction
Where:
Direction = 1 for long trades
Direction = -1 for short trades
Net profit must include every relevant trading cost:
Net P&L = Gross P&L - Commission - Spread Cost - Slippage - Borrow Cost - Funding Cost
For margin-based or derivative products, also consider:
- Overnight financing
- Futures exchange and clearing fees
- Stock borrow charges
- Options assignment or exercise costs
- Currency conversion costs
- Margin interest
- Contract rollover costs
A Simple Python Backtest Using a Historical Trade Log
The following example calculates net profit, equity and drawdown from completed long and short trades.
import pandas as pd
import numpy as np
STARTING_CAPITAL = 10_000
SLIPPAGE_BPS = 5 # 5 basis points per entry and exit notional
df = pd.read_csv(
"historical_trades.csv",
parse_dates=["entry_time", "exit_time"]
)
required_columns = {
"symbol",
"side",
"entry_time",
"exit_time",
"entry_price",
"exit_price",
"quantity"
}
missing = required_columns - set(df.columns)
if missing:
raise ValueError(f"Missing required columns: {sorted(missing)}")
df = df.sort_values("exit_time").reset_index(drop=True)
if (df["exit_time"] <= df["entry_time"]).any():
raise ValueError("Every exit_time must occur after entry_time.")
if (df["quantity"] <= 0).any():
raise ValueError("Quantity must be greater than zero.")
df["direction"] = (
df["side"].str.lower().map({"long": 1, "short": -1})
)
if df["direction"].isna().any():
raise ValueError("side must contain only 'long' or 'short'.")
if "fees" not in df.columns:
df["fees"] = 0.0
df["gross_pnl"] = (
(df["exit_price"] - df["entry_price"])
* df["quantity"]
* df["direction"]
)
entry_notional = df["entry_price"] * df["quantity"]
exit_notional = df["exit_price"] * df["quantity"]
df["slippage_cost"] = (
(entry_notional + exit_notional)
* SLIPPAGE_BPS
/ 10_000
)
df["net_pnl"] = (
df["gross_pnl"]
- df["fees"]
- df["slippage_cost"]
)
df["equity"] = STARTING_CAPITAL + df["net_pnl"].cumsum()
df["equity_peak"] = df["equity"].cummax()
df["drawdown"] = (
df["equity"] / df["equity_peak"] - 1
)
total_net_pnl = df["net_pnl"].sum()
total_return = total_net_pnl / STARTING_CAPITAL
max_drawdown = df["drawdown"].min()
winning_trades = df.loc[df["net_pnl"] > 0, "net_pnl"]
losing_trades = df.loc[df["net_pnl"] < 0, "net_pnl"]
profit_factor = (
winning_trades.sum() / abs(losing_trades.sum())
if len(losing_trades) > 0
else np.inf
)
summary = {
"trades": len(df),
"net_pnl": total_net_pnl,
"total_return": total_return,
"win_rate": (df["net_pnl"] > 0).mean(),
"average_trade": df["net_pnl"].mean(),
"profit_factor": profit_factor,
"max_drawdown": max_drawdown,
"ending_equity": df["equity"].iloc[-1]
}
print(pd.Series(summary))
This code measures the recorded trades. It does not recreate unrecorded signals, price movement while a trade was open or portfolio exposure between trade exits.
How to Model Realistic Execution
A backtest should use the price the strategy could have received, not automatically use the last traded price.
Spread
A buy order usually executes near the ask, while a sell order usually executes near the bid. If bid and ask data is available, use it directly.
Examples:
- Market buy: execute at the ask
- Market sell: execute at the bid
- Limit buy: fill at the limit or a better price only when the historical data supports that fill
- Limit sell: fill at the limit or a better price only when the historical data supports that fill
Using the midpoint for every transaction generally makes the result look better than live execution.
Slippage
Slippage is the difference between the intended price and the fill price. It tends to increase during:
- High volatility
- Low liquidity
- Market openings
- Market closings
- News releases
- Large orders
Run the backtest under several slippage assumptions. A strategy that remains profitable after higher costs has more room for execution error than one that works only at an ideal price.
Latency
If a signal appears at 10:00:00 but the order reaches the market at 10:00:02, use prices available at 10:00:02. This matters for:
- Short-term futures systems
- High-frequency systems
- News trading
- Breakout systems
- Strategies with small profit targets
Partial Fills and Liquidity
Do not assume that every order fills in full at one price. Depending on the strategy, the model may need to account for:
- Available volume at each price
- Maximum participation rate
- Order size relative to average volume
- Queue position for limit orders
- Multiple fills
- Unfilled orders
How to Build the Equity Curve Correctly
A closed-trade equity curve adds each completed trade's net profit in exit-time order:
Equity after trade n = Starting capital + cumulative net P&L through trade n
This is useful for reviewing completed trades, but it can hide losses that occurred before a position closed.
Closed-Trade Equity Curve
A closed-trade curve changes only when a trade closes. It does not show the unrealised gain or loss during an open position.
Mark-to-Market Equity Curve
A mark-to-market curve values open positions at regular intervals, such as every minute, hour or day. It gives a clearer view of:
- Intraday drawdown
- Overnight risk
- Margin usage
- Portfolio exposure
- Time spent in the market
- Simultaneous positions
When open-position risk matters, use mark-to-market equity instead of relying only on closed-trade results.
Do not annualise returns using only the number of trades. Use the calendar period or a time-based equity curve.
Which Performance Metrics Should You Calculate?
No single metric can establish that a strategy is viable. Review returns, risk, trade results and exposure together.
Return Metrics
Calculate:
- Total net profit
- Total return
- Compound annual growth rate
- Average monthly return
- Return relative to a relevant benchmark
Risk Metrics
Calculate:
- Maximum drawdown
- Average drawdown
- Drawdown duration
- Downside deviation
- Worst trade
- Worst day or month
- Value at Risk or expected shortfall, where appropriate
Calculate maximum drawdown from the equity curve, not only from individual losing trades.
Trade Metrics
Calculate:
- Number of trades
- Win rate
- Average winning trade
- Average losing trade
- Largest winning trade
- Largest losing trade
- Profit factor
- Expectancy
- Median trade
- Payoff ratio
- Consecutive wins and losses
Expectancy is:
Expectancy =
(Win Rate × Average Win)
-
(Loss Rate × Average Loss)
Check how average loss is stored. If it is already a negative number, subtracting it again will produce the wrong result.
Exposure and Capacity Metrics
Also measure:
- Average time in the market
- Maximum concurrent positions
- Average margin use
- Turnover
- Average position size
- Percentage of time invested
- Profit by instrument
- Profit by market condition
A strategy can produce a high return while tying up capital for long periods.
How to Avoid Look-Ahead Bias
Look-ahead bias occurs when a backtest uses information that was not available when the trade decision was made.
Common examples include:
- Using the day's closing price to enter earlier that day
- Calculating an indicator with future data
- Filling an order at a price that appeared after the signal
- Using revised economic data instead of the original release
- Selecting the best-performing symbols after reviewing the test period
- Applying adjusted data without accounting for the timing of corporate actions
Every feature, signal and execution decision must use information available before the order was placed.
How to Split Data Into Training and Testing Periods
Use chronological periods rather than randomly shuffling trades.
A practical structure is:
- In-sample period: Develop the strategy and select parameters.
- Validation period: Compare alternative rules or parameter sets.
- Out-of-sample period: Test the final strategy once on unseen data.
- Forward test: Run the strategy in real time or with delayed execution before risking capital.
For example, an earlier period can be used for development, a later period for validation and the most recent untouched period for the final test. The exact percentages matter less than preserving the time order and keeping the final test independent.
Do not keep checking the out-of-sample results and then changing the rules. Once the test data influences the strategy, it is no longer a genuine out-of-sample test.
How to Test Whether Results Hold Up
A credible backtest should continue to make sense when reasonable assumptions change.
Change Transaction Costs
Run the test with:
- Base commission
- Higher commission
- Wider spread
- Higher slippage
- Additional financing or borrow costs
If a small cost increase removes all profits, the strategy has little margin for error.
Test Different Market Periods
Include conditions such as:
- Bull markets
- Bear markets
- Sideways markets
- High-volatility periods
- Low-volatility periods
- Rising and falling interest-rate environments
A strategy does not have to make money in every period. Its losing periods should be understood and controlled.
Test Nearby Parameters
If a moving-average strategy works only with one exact parameter combination, it may be fitted to historical noise. Test nearby values and look for a stable area of performance rather than one isolated peak.
Use Trade Resampling
Bootstrap or resample the trade sequence to estimate how the order of wins and losses affects drawdown. This does not create new market data. It shows whether the reported drawdown depends on one unusually favourable sequence.
Common Historical Trade-Data Backtesting Mistakes
Treating Recorded Trades as Proof of Signal Quality
A profitable trade log may contain only selected trades. It does not show what happened when signals were ignored or unavailable.
Ignoring the Bid-Ask Spread
Buying at the ask and selling at the bid can materially change short-term results. Using the last-traded price for both sides can overstate profit.
Measuring Drawdown Only After Trades Close
An open position can lose money before recovering. Use mark-to-market data when intratrade risk matters.
Including Costs Only Once
Many strategies pay costs at entry and exit. Derivative and margin-based strategies may also incur financing, funding or rollover charges.
Randomly Splitting Time-Series Data
Random train-test splits allow future conditions to influence the training sample. Use chronological splits.
Removing Losing Instruments
Excluding delisted or failed securities creates survivorship bias and makes historical results look better than they were.
Optimising Too Many Parameters
Extra filters, indicators and exceptions can make a strategy fit historical noise. Keep the rules explainable and test them on unseen data.
Reporting Only the Average Return
Average return does not show volatility, drawdown, tail losses, exposure or sample size. Report net results alongside risk and the number of trades.
Minimum Checklist Before Trusting a Backtest
Before using historical trade data to make a trading decision, confirm that:
- Entry and exit timestamps are correct.
- The dataset uses one consistent timezone.
- Long and short P&L are calculated separately and correctly.
- Commission, spread and slippage are included.
- Financing, borrow and rollover costs are included where relevant.
- Prices are adjusted consistently for corporate actions.
- Delisted instruments are not automatically excluded.
- Open-position drawdown is measured when necessary.
- The strategy uses no future information.
- The final test period was not used for optimisation.
- Results remain acceptable under worse execution assumptions.
- The trade count is large enough to support the conclusion.
- Performance does not depend on one unusually profitable trade or a short period.
Best Workflow for Backtesting Historical Trade Data
Use this sequence:
- Define the strategy rules in plain language.
- Identify whether the dataset is a trade log, candle data, tick data or quote data.
- Standardise timestamps, symbols, prices and quantities.
- Remove duplicates and investigate missing or impossible records.
- Reconstruct entries, exits and position sizing.
- Apply spread, commission, slippage and financing costs.
- Build closed-trade and mark-to-market equity curves where possible.
- Calculate return, drawdown, trade and exposure metrics.
- Test the strategy on a chronological out-of-sample period.
- Stress-test costs, parameters and market conditions.
- Forward-test the final rules before committing capital.
A historical trade log tells you how recorded trades performed. Full strategy backtesting requires the market data and rules needed to recreate both the trades that were taken and the opportunities that were missed.