#!pip install fastquant
/Users/enzoampil/quant/fastquant

fastquant Lesson 2 - Backtest your trading strategy with only 3 lines of code

fastquant package Updates

  1. get_stock_data has been added, which now includes all Yahoo Finance data (on top of PSE)
  2. backtest function is now ready to use!
  3. README has been updated to reflect the above

Setup

  1. Google Colab (limitted plotting)
  2. Jupyter notebook (complete plotting)

Colab notebook on this link

How to use Jupyter notebooks

1. Go to your terminal and install jupyter

pip3 install jupyter

2. Clone fastquant

git clone https://github.com/enzoampil/fastquant.git

cd fastquant

3. Run jupyter

jupyter notebook

4. Open lesson at lessons/fastquant_lesson2_backtest_your_trading_strategy.ipynb

Recap from Lesson 1

Plot Closing Stock Prices

from matplotlib import pyplot as plt
from fastquant import backtest, get_stock_data
%matplotlib inline
jfc = get_stock_data("JFC", "2018-01-01", "2019-01-01")
jfc.close.plot(figsize=(10, 6))
plt.title("Daily Closing Prices of JFC\nfrom 2018-01-01 to 2019-01-01", fontsize=20)
Reading cached file found: JFC_2018-01-01_2019-01-01.csv
Text(0.5, 1.0, 'Daily Closing Prices of JFC\nfrom 2018-01-01 to 2019-01-01')

Analyze with a simple moving average (SMA) trading strategy

In this section, we will attempt to visually assess the performance of a SMA crossover strategy. There are many ways to do this strategy, but we will go with a “price crossover” approach with a 30 day SMA.

In this case, it’s considered a “buy” signal when the closing price crosses the simple moving average from below, and considered a “sell” signal when the closing price crosses the simple moving average from above.

So how do we know our SMA price crossover strategy is effective? Visually, we can assess this by seeing if the “sell” signal happens right before the stock price starts going down, and if the “buy” signal happens right before the stock price starts going up.

import pandas as pd

ma30 = jfc.close.rolling(30).mean()
close_ma30 = pd.concat([jfc.close, ma30], axis=1).dropna()
close_ma30.columns = ['Closing Price', 'Simple Moving Average (30 day)']

close_ma30.plot(figsize=(10, 6))
plt.title("Daily Closing Prices vs 30 day SMA of JFC\nfrom 2018-01-01 to 2019-01-01", fontsize=20)
Text(0.5, 1.0, 'Daily Closing Prices vs 30 day SMA of JFC\nfrom 2018-01-01 to 2019-01-01')

Lesson 2: Backtesting with fastquant

Backtest your trading strategy in 3 lines of code

Now, let's get started with backtesting!

Below, I show how you can use fastquant to backtest a simple moving average crossover (similar to what we have above).

The three steps are:

  1. Import the backtest and get_pse_data functions from fastquant
from fastquant import backtest, get_stock_data
  1. Get stock data in a date, close, volume format (DCV)

Here, we get DCV (date, closing, volume) data from JFC using the get_pse_data function

jfc = get_stock_data("JFC", "2018-01-01", "2019-01-01")
Reading cached file found: JFC_2018-01-01_2019-01-01.csv
jfc
dt close volume
0 2018-01-03 255.4 745780
1 2018-01-04 255.0 617010
2 2018-01-05 255.0 946040
3 2018-01-08 256.0 840630
4 2018-01-09 255.8 978180
... ... ... ...
238 2018-12-20 303.0 659480
239 2018-12-21 302.4 715510
240 2018-12-26 292.0 1087620
241 2018-12-27 295.0 585760
242 2018-12-28 291.8 425440

243 rows × 3 columns

  1. Backtest a simple moving average crossover (smac) strategy on the JFC data

We perform the backtest using a 15 day moving average as the "fast" moving average, and a 35 day moving average as the "slow" moving average. If we want to change these parameter values, we can just replace the numbers in the backtest function.

We call these the strategy level arguments since they are unique to a specific strategy.

Do note that by default, the backtesting algorithm assumes that you start out with PHP 100,000 as cash (init_cash), while using all of that cash during a buy signal (buy_prop), and selling all of your current stock holdings during a sell signal (sell_prop). We call these the global level arguments since they can be shared across companies.

%matplotlib
backtest('smac', jfc, fast_period=15, slow_period=35)
Using matplotlib backend: MacOSX
Starting Portfolio Value: 100000.00
===Global level arguments===
init_cash : 100000
buy_prop : 1
sell_prop : 1
===Strategy level arguments===
fast_period : 15
slow_period : 35
2018-08-06, BUY CREATE, 280.00
2018-08-06, Cash: 100000.0
2018-08-06, Price: 280.0
2018-08-06, Buy prop size: 354
2018-08-06, Afforded size: 354
2018-08-06, Final size: 354
2018-08-07, BUY EXECUTED, Price: 280.00, Cost: 99120.00, Comm 743.40
2018-09-20, SELL CREATE, 272.00
2018-09-21, SELL EXECUTED, Price: 272.00, Cost: 99120.00, Comm 722.16
2018-09-21, OPERATION PROFIT, GROSS -2832.00, NET -4297.56
2018-10-31, BUY CREATE, 276.00
2018-10-31, Cash: 95702.44
2018-10-31, Price: 276.0
2018-10-31, Buy prop size: 343
2018-10-31, Afforded size: 343
2018-10-31, Final size: 343
2018-11-05, BUY EXECUTED, Price: 276.00, Cost: 94668.00, Comm 710.01
Final Portfolio Value: 100411.83

Customize strategy parameters

The idea of backtesting is that we should choose the best strategy based on which one has worked best over time.

%matplotlib
backtest('smac', jfc, fast_period=1, slow_period=30)
Using matplotlib backend: MacOSX
Starting Portfolio Value: 100000.00
===Global level arguments===
init_cash : 100000
buy_prop : 1
sell_prop : 1
===Strategy level arguments===
fast_period : 1
slow_period : 30
2018-02-26, BUY CREATE, 292.00
2018-02-26, Cash: 100000.0
2018-02-26, Price: 292.0
2018-02-26, Buy prop size: 339
2018-02-26, Afforded size: 339
2018-02-26, Final size: 339
2018-02-27, BUY EXECUTED, Price: 292.00, Cost: 98988.00, Comm 742.41
2018-03-14, SELL CREATE, 284.00
2018-03-15, SELL EXECUTED, Price: 284.00, Cost: 98988.00, Comm 722.07
2018-03-15, OPERATION PROFIT, GROSS -2712.00, NET -4176.48
2018-03-16, BUY CREATE, 305.40
2018-03-16, Cash: 95823.51999999999
2018-03-16, Price: 305.4
2018-03-16, Buy prop size: 311
2018-03-16, Afforded size: 311
2018-03-16, Final size: 311
2018-03-19, BUY EXECUTED, Price: 305.40, Cost: 94979.40, Comm 712.35
2018-03-20, SELL CREATE, 285.00
2018-03-21, SELL EXECUTED, Price: 285.00, Cost: 94979.40, Comm 664.76
2018-03-21, OPERATION PROFIT, GROSS -6344.40, NET -7721.51
2018-03-26, BUY CREATE, 295.00
2018-03-26, Cash: 88102.012
2018-03-26, Price: 295.0
2018-03-26, Buy prop size: 296
2018-03-26, Afforded size: 296
2018-03-26, Final size: 296
2018-03-27, BUY EXECUTED, Price: 295.00, Cost: 87320.00, Comm 654.90
2018-04-04, SELL CREATE, 280.00
2018-04-05, SELL EXECUTED, Price: 280.00, Cost: 87320.00, Comm 621.60
2018-04-05, OPERATION PROFIT, GROSS -4440.00, NET -5716.50
2018-04-12, BUY CREATE, 300.00
2018-04-12, Cash: 82385.512
2018-04-12, Price: 300.0
2018-04-12, Buy prop size: 272
2018-04-12, Afforded size: 272
2018-04-12, Final size: 272
2018-04-13, BUY EXECUTED, Price: 300.00, Cost: 81600.00, Comm 612.00
2018-04-13, SELL CREATE, 291.60
2018-04-16, SELL EXECUTED, Price: 291.60, Cost: 81600.00, Comm 594.86
2018-04-16, OPERATION PROFIT, GROSS -2284.80, NET -3491.66
2018-04-18, BUY CREATE, 294.20
2018-04-18, Cash: 78893.84800000001
2018-04-18, Price: 294.2
2018-04-18, Buy prop size: 265
2018-04-18, Afforded size: 265
2018-04-18, Final size: 265
2018-04-19, BUY EXECUTED, Price: 294.20, Cost: 77963.00, Comm 584.72
2018-04-19, SELL CREATE, 291.00
2018-04-20, SELL EXECUTED, Price: 291.00, Cost: 77963.00, Comm 578.36
2018-04-20, OPERATION PROFIT, GROSS -848.00, NET -2011.08
2018-04-23, BUY CREATE, 294.00
2018-04-23, Cash: 76882.763
2018-04-23, Price: 294.0
2018-04-23, Buy prop size: 259
2018-04-23, Afforded size: 259
2018-04-23, Final size: 259
2018-04-24, BUY EXECUTED, Price: 294.00, Cost: 76146.00, Comm 571.09
2018-04-24, SELL CREATE, 281.60
2018-04-25, SELL EXECUTED, Price: 281.60, Cost: 76146.00, Comm 547.01
2018-04-25, OPERATION PROFIT, GROSS -3211.60, NET -4329.70
2018-05-16, BUY CREATE, 286.60
2018-05-16, Cash: 72553.06000000001
2018-05-16, Price: 286.6
2018-05-16, Buy prop size: 251
2018-05-16, Afforded size: 251
2018-05-16, Final size: 251
2018-05-17, BUY EXECUTED, Price: 286.60, Cost: 71936.60, Comm 539.52
2018-05-17, SELL CREATE, 285.00
2018-05-18, SELL EXECUTED, Price: 285.00, Cost: 71936.60, Comm 536.51
2018-05-18, OPERATION PROFIT, GROSS -401.60, NET -1477.64
2018-05-18, BUY CREATE, 285.00
2018-05-18, Cash: 71075.42300000001
2018-05-18, Price: 285.0
2018-05-18, Buy prop size: 247
2018-05-18, Afforded size: 247
2018-05-18, Final size: 247
2018-05-21, BUY EXECUTED, Price: 285.00, Cost: 70395.00, Comm 527.96
2018-05-21, SELL CREATE, 284.00
2018-05-22, SELL EXECUTED, Price: 284.00, Cost: 70395.00, Comm 526.11
2018-05-22, OPERATION PROFIT, GROSS -247.00, NET -1301.07
2018-05-23, BUY CREATE, 287.00
2018-05-23, Cash: 69774.35050000002
2018-05-23, Price: 287.0
2018-05-23, Buy prop size: 241
2018-05-23, Afforded size: 241
2018-05-23, Final size: 241
2018-05-24, BUY EXECUTED, Price: 287.00, Cost: 69167.00, Comm 518.75
2018-05-24, SELL CREATE, 281.20
2018-05-25, SELL EXECUTED, Price: 281.20, Cost: 69167.00, Comm 508.27
2018-05-25, OPERATION PROFIT, GROSS -1397.80, NET -2424.82
2018-06-01, BUY CREATE, 283.80
2018-06-01, Cash: 67349.52900000001
2018-06-01, Price: 283.8
2018-06-01, Buy prop size: 235
2018-06-01, Afforded size: 235
2018-06-01, Final size: 235
2018-06-04, BUY EXECUTED, Price: 283.80, Cost: 66693.00, Comm 500.20
2018-06-04, SELL CREATE, 280.20
2018-06-05, SELL EXECUTED, Price: 280.20, Cost: 66693.00, Comm 493.85
2018-06-05, OPERATION PROFIT, GROSS -846.00, NET -1840.05
2018-06-07, BUY CREATE, 282.60
2018-06-07, Cash: 65509.479000000014
2018-06-07, Price: 282.6
2018-06-07, Buy prop size: 229
2018-06-07, Afforded size: 229
2018-06-07, Final size: 229
2018-06-08, BUY EXECUTED, Price: 282.60, Cost: 64715.40, Comm 485.37
2018-06-13, SELL CREATE, 279.20
2018-06-14, SELL EXECUTED, Price: 279.20, Cost: 64715.40, Comm 479.53
2018-06-14, OPERATION PROFIT, GROSS -778.60, NET -1743.49
2018-06-19, BUY CREATE, 284.00
2018-06-19, Cash: 63765.98750000001
2018-06-19, Price: 284.0
2018-06-19, Buy prop size: 222
2018-06-19, Afforded size: 222
2018-06-19, Final size: 222
2018-06-20, BUY EXECUTED, Price: 284.00, Cost: 63048.00, Comm 472.86
2018-06-20, SELL CREATE, 270.00
2018-06-21, SELL EXECUTED, Price: 270.00, Cost: 63048.00, Comm 449.55
2018-06-21, OPERATION PROFIT, GROSS -3108.00, NET -4030.41
2018-07-27, BUY CREATE, 266.00
2018-07-27, Cash: 59735.57750000001
2018-07-27, Price: 266.0
2018-07-27, Buy prop size: 222
2018-07-27, Afforded size: 222
2018-07-27, Final size: 222
2018-07-30, BUY EXECUTED, Price: 266.00, Cost: 59052.00, Comm 442.89
2018-09-06, SELL CREATE, 281.00
2018-09-07, SELL EXECUTED, Price: 281.00, Cost: 59052.00, Comm 467.87
2018-09-07, OPERATION PROFIT, GROSS 3330.00, NET 2419.24
2018-09-07, BUY CREATE, 281.00
2018-09-07, Cash: 62154.82250000001
2018-09-07, Price: 281.0
2018-09-07, Buy prop size: 219
2018-09-07, Afforded size: 219
2018-09-07, Final size: 219
2018-09-10, BUY EXECUTED, Price: 281.00, Cost: 61539.00, Comm 461.54
2018-09-11, SELL CREATE, 269.00
2018-09-12, SELL EXECUTED, Price: 269.00, Cost: 61539.00, Comm 441.83
2018-09-12, OPERATION PROFIT, GROSS -2628.00, NET -3531.38
2018-10-19, BUY CREATE, 265.00
2018-10-19, Cash: 58623.44750000001
2018-10-19, Price: 265.0
2018-10-19, Buy prop size: 219
2018-10-19, Afforded size: 219
2018-10-19, Final size: 219
2018-10-22, BUY EXECUTED, Price: 265.00, Cost: 58035.00, Comm 435.26
Final Portfolio Value: 64057.39
%matplotlib
backtest('smac', jfc, fast_period=30, slow_period=50)
Using matplotlib backend: MacOSX
Starting Portfolio Value: 100000.00
===Global level arguments===
init_cash : 100000
buy_prop : 1
sell_prop : 1
===Strategy level arguments===
fast_period : 30
slow_period : 50
2018-08-23, BUY CREATE, 293.00
2018-08-23, Cash: 100000.0
2018-08-23, Price: 293.0
2018-08-23, Buy prop size: 338
2018-08-23, Afforded size: 338
2018-08-23, Final size: 338
2018-08-24, BUY EXECUTED, Price: 293.00, Cost: 99034.00, Comm 742.75
2018-10-05, SELL CREATE, 243.00
2018-10-08, SELL EXECUTED, Price: 243.00, Cost: 99034.00, Comm 616.00
2018-10-08, OPERATION PROFIT, GROSS -16900.00, NET -18258.76
2018-11-19, BUY CREATE, 282.00
2018-11-19, Cash: 81741.23999999999
2018-11-19, Price: 282.0
2018-11-19, Buy prop size: 287
2018-11-19, Afforded size: 287
2018-11-19, Final size: 287
2018-11-20, BUY EXECUTED, Price: 282.00, Cost: 80934.00, Comm 607.00
Final Portfolio Value: 83946.83

We can conclude that across all the parameter combinations we've tried, the best performing one is the one where fast_period = 15, while slow_period = 40.

%matplotlib
backtest('smac', jfc, fast_period=15, slow_period=40)
Using matplotlib backend: MacOSX
Starting Portfolio Value: 100000.00
===Global level arguments===
init_cash : 100000
buy_prop : 1
sell_prop : 1
===Strategy level arguments===
fast_period : 15
slow_period : 40
2018-08-07, BUY CREATE, 270.00
2018-08-07, Cash: 100000.0
2018-08-07, Price: 270.0
2018-08-07, Buy prop size: 367
2018-08-07, Afforded size: 367
2018-08-07, Final size: 367
2018-08-08, BUY EXECUTED, Price: 270.00, Cost: 99090.00, Comm 743.17
2018-09-21, SELL CREATE, 271.00
2018-09-24, SELL EXECUTED, Price: 271.00, Cost: 99090.00, Comm 745.93
2018-09-24, OPERATION PROFIT, GROSS 367.00, NET -1122.10
2018-11-05, BUY CREATE, 280.00
2018-11-05, Cash: 98877.89749999999
2018-11-05, Price: 280.0
2018-11-05, Buy prop size: 350
2018-11-05, Afforded size: 350
2018-11-05, Final size: 350
2018-11-06, BUY EXECUTED, Price: 280.00, Cost: 98000.00, Comm 735.00
Final Portfolio Value: 102272.90

Relevant resources

  1. Medium article
  2. Backtrader repo

How to contribute more trading strategies

Checkout fastquant/strategies.py

  1. New strategies can be added with as little as 20 lines of code (excluding docs)
  2. Please let me know if you're intrested in contributing! :)
  3. Happy to walk you through the process personally, still in the process of writing a tutorial

Planning to add the following TA strategies next:

  1. SMAC [DONE]
  2. RSI [DONE]
  3. Bollinger Bands
  4. Moving Average Convergence Divergence
  5. EMAC (exponential moving average crossover)
  6. On-Balance-Volume
  7. Ichimoku Kinko Hyo
  8. Average Directional Index
  9. Parabolic Stop and Reverse (SAR)
  10. Stochastic

For advanced DS peeps who want to go straight into ML based indicators, I also encourage you to contribute!

Next webinar - Lecture 3: Relative Strength Index (Theory + backtesting)