systematic trading uses data (primarily price and volume) to discover patterns, test whether they endure, control risk continuously and execute with discipline and consistency
What is Systematic Trading
rule based trading or mechanical trading
auto (machines) / algotrading
semi-auto (human and machines)
manual (humans)
rules → variables → parameters
you don’t trade stocks, you trade rules
backtest
risk management
elimination of emotions
after all, it’s humans who trade
decisions are driven by desires - greed and fear
traditional models → behavioural finance → social finance
Backtesting
simulate a trading rule on historical data to evaluate how it would have performed
Backtesting Steps
get historical data
simulate your trading in historical data
identify the signals
record buy / sell points
calculate the PNL and other metrics
fit variations on historical period and finalise the best model
assumes historical patterns may persist; good past performance does not guarantee future results
different ways to backtest
Backtesting Types
in sample backtest
choose the best variation using all N years, then report performance on those same years
results can look too good due to overfitting; earlier years are evaluated using a rule selected with later data
out of sample backtest
split chronologically into an earlier training (in sample) period and a later test (out of sample) period
optimise and select the variation on training data, then evaluate it on unseen test data
holds back data from training; a fixed model does not adapt to market changes during testing
expanding window
fit on all available past data, then test on the next period (e.g. 1 year)
add that period to training, refit and test on the following period; the training window grows
e.g. train years 1–3 → test 4; train 1–4 → test 5
rolling window
use a fixed-length training window; move it forward, dropping the oldest data, then refit and test
e.g. train years 1–3 → test 4; train 2–4 → test 5
focuses on recent market conditions, but uses less training data
expanding and rolling windows are walk-forward testing: each test period uses only earlier data for fitting
legend: columns = years; rows = test stages; dark grey = fitting data; square = test period; light grey = unused for fitting
top left: in sample: fit on all years, then test those same years; includes future data relative to earlier tests
top right: out of sample: fit once on 1990–1994, then test later years without refitting
bottom left: expanding: refit using all past data before each test; training window grows
bottom right: rolling: once the window reaches its fixed length, move it forward and drop the oldest data before refitting
Variations in Backtesting
variation: a different parameter setting for a trading rule, e.g. different moving-average lengths
with hundreds of variations, options include choosing the best, choosing randomly, or combining variations with equal / unequal weights
the best historical result may reflect luck rather than a better rule; testing more variations increases the chance of finding a misleading winner (overfitting)
another naive approach: select variations with similar performance in training and test periods, then combine them
if test results influence selection, that period is no longer an independent test; evaluate the final choice on fresh unseen data
How much data is needed?
to trust in sharpe ratio, how much data is needed?
experiment: generated returns for a positive sharpe. keep on measuring the SR as the new data comes and then measure the distribution and mean
2 sigma test. if estimated SR > 2 sigma, only a 2.5% chance of being negative
t-test for profitability
Best Practices
small number of rules
few variations. look at returns, if highly correlated, drop the variations
select variations that show similar result in both train and test periods individually
assign weights to each variation
equal weights or optimised weights or bootstrapped weights
for midterm, single variation will be ok
Vectorised vs Event Based Backtesting
Vectorised Backtesting
computationally efficient and easy to implement
focuses on signal generation and deals with overall returns
uses daily returns to evaluate signal profitability
no explicit modelling of trade execution (no quantities, transaction fees, leverage etc)
calculations performed using dataframes
Event-Based Backtesting
more realistic simulation of trading strategies
explicitly models trade execution and portfolio dynamics
tracks profits and losses, cash balances and position sizes
incorporates risk management and money management rules
calculations using loops and advanced coding techniques
MA Cross in python
Install and import libraries
Create initial helper variables like dates and symbols for backtesting
Check if the data is OK
Define the RULES
Visualise if signals exists
Create dataframe to store data and perform vectorised backtesting
Create a column “Position” based on your Rule
Create a column “Strategy Returns “ based on ‘Position’ and ‘Stock returns’.
Calculate Mean, Standard deviation and Sharpe Ratio
on both Stock and Strategy column
Notebook code snippets
rule → variables → parameters: compare fast / slow moving averages, using 5 / 21 trading days
shift(1) is essential: today’s close determines today’s position, which earns the next period’s return; using today’s position on today’s return introduces look-ahead bias
follows the notebook’s simplified execution at the signal close, without costs; multiplying log returns by −1 is an approximation for short-position returns
log returns add over time; exp(cumsum()) gives growth of 1 unit, while exp(sum()) - 1 gives total return
annualisation: daily mean × 252; daily standard deviation × √252. The notebook’s Sharpe calculation assumes a zero risk-free rate and uses an annual return converted from log returns
Drawdown
equity = growth["Strategy_Returns"]peak = equity.cummax().clip(lower=1.0) # include initial wealth of 1drawdown = peak - equitymax_drawdown = drawdown.max()
cummax() tracks the highest value reached so far; drawdown is the fall below that peak
the notebook measures an absolute gap in wealth units; percentage drawdown is 1 - equity / peak
its longest drawdown calculation uses gaps between peak dates; it misses an unrecovered drawdown at the end