adein.dev

Menguji strategi trading dengan backtesting

Jika Anda kebetulan adalah seorang yang pernah melakukan trading instrumen saham, mungkin sudah akrab dengan beberapa indikator teknikal, seperti Simple Moving Average, Exponential Moving Average, Bollinger Bands, RSI Oscillator, dll. Melalui observasi chart pattern maupun data perdagangan historis, indikator-indikator tersebut seringkali digunakan oleh para trader untuk menyusun strategi trading.

Dalam realita yang biasanya saya perhatikan, ketika seseorang telah merancang strategi trading, seringkali mereka langsung mengimplementasikan strategi tersebut ke dalam aktivitas trading yang sesungguhnya (alias dengan real money). Dalam perspektif manajemen risiko, tentu ini bukan merupakan sesuatu yang bisa dibilang good practice. Ada beberapa alasan yang umumnya digunakan orang-orang untuk membenarkan perilaku ini. Pertama, karena orang tersebut merupakan trader individu yang tidak terikat dalam SOP tertentu. Kedua, langsung menerapkan strategi dalam aktivitas trading yang nyata merupakan cara tercepat untuk tahu apakah strategi tersebut jitu atau tidak. Ketiga, eksperimen dilakukan dengan modal yang kecil, jadi jika ternyata strateginya gagal, rugi yang dialami juga tidak akan banyak.

Kecenderungan untuk langsung menerapkan strategi ke dalam aktivitas trading yang sesungguhnya tidak lepas dari yang namanya optimism bias. Ada perasaan superior ketika kita telah menemukan suatu strategi yang kita anggap pasti akan menguntungkan dan tidak diketahui oleh orang lain sebelumnya. Sehingga kita mengabaikan hal-hal buruk yang mungkin dapat terjadi.

Tapi sekali lagi, kebiasaan seperti itu tidak mencerminkan disiplin yang baik sebagai seorang investor atau pun trader. Seorang investor/trader yang telah memiliki disiplin yang matang umumnya memiliki serangkaian aktivitas dalam pipeline kegiatan investasinya agar seluruh investment thesis yang dibuat, termasuk strategi trading tersebut dapat dipertanggungjawabkan, seperti dokumentasi kegiatan analisis (makro/mikroekonomi, laporan keuangan/chart pattern), journaling, simulasi trading, maupun backtesting. Cukup banyak & merepotkan? Ya memang. But believe it or not, sebagian besar rutinitas investor/trader kelas kakap bukan terletak pada saat dia menjual/membeli instrumen investasi, namun pada aktivitas-aktivitas tersebut.

Tulisan ini akan memberikan sedikit gambaran mengenai apa itu backtesting. Jika dalam software engineering ada istilah unit testing, yaitu proses pengujian kode yang ditulis apakah bisa berjalan sesuai dengan apa diharapkan, maka di dalam quantitative/algorithmic trading ada istilah backtesting. Backtesting itu sendiri merupakan proses untuk menguji keampuhan sebuah strategi trading dengan menggunakan data historis. Singkatnya, proses backtesting seperti menjawab apakah strategi kita benar-benar jitu & dapat meraih return yang diharapkan jika digunakan pada masa lalu?

Yang membuat backtesting ini lebih unggul daripada simulasi trading adalah lingkup waktu yang diuji. Dalam simulasi trading, kita menguji strategi dengan menggunakan modal virtual pada aktivitas market yang riil. Karena diuji pada situasi yang nyata, maka jika kita ingin mengetahui keampuhan strategi kita dalam periode tertentu, katakanlah seminggu atau sebulan, maka kita harus menunggu juga selama itu (karena mengikuti jadwal bursa). Berbeda dengan backtesting, karena berorientasi pada data historis, kita dapat menguji strategi yang kita buat dalam periode berapapun, selama datanya tersedia. Jadi hasil dari pengujian dapat diketahui secara instan.

Okay, kita langsung menuju praktek. Jika Anda sudah familiar dengan bahasa pemrograman Python, maka seharusnya kegiatan backtesting ini tidak terlalu sulit untuk Anda. Ada banyak sekali library dalam bahasa Python yang dibuat untuk melakukan backtesting, namun yang akan kita gunakan saat ini adalah library Backtesting.py. Ada 2 alasan kenapa library ini dipilih, pertama karena penggunaannya mudah. Kedua, library ini sudah memberikan fitur yang cukup lengkap untuk proses backtesting, termasuk visualisasi interaktifnya.

Lalu katakanlah kita memiliki sebuah strategi trading sederhana seperti ini:

“Kita akan membeli saham Bank BCA (BBCA) jika rata-rata harga saham tersebut di 10 periode terakhir (Simple Moving Average/SMA(10)) melampaui rata-rata harga saham di 20 periode terakhir (SMA(20)), dan sebaliknya jika rata-rata harga saham di SMA(20) melampaui SMA(10), maka akan kita jual”.

Dari strategi tersebut, maka kode Python untuk backtesting yang kita lakukan adalah sebagai berikut:

from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
import yfinance as yf

stock_symbol = "BBCA.JK"
start_date = "2021-01-01"
end_date = "2023-12-31"

data = yf.download(stock_symbol, start=start_date, end=end_date)

class SmaCross(Strategy):
    n1 = 10
    n2 = 20

    def init(self):
        close = self.data.Close
        self.sma1 = self.I(SMA, close, self.n1)
        self.sma2 = self.I(SMA, close, self.n2)

    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.sell()

bt = Backtest(data, SmaCross,
              cash=10000000, commission=.002,
              exclusive_orders=True)

output = bt.run()
bt.plot()

Mari kita bedah kode di atas secara lebih rinci:

from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
import yfinance as yf

Kode di atas adalah kode untuk meng-import bagian-bagian kode yang akan kita butuhkan. Dengan asumsi kita sudah meng-install library Backtesting.py ke dalam environment Python kita. Kita juga meng-import library yfinance untuk mengunduh data historis. Di sini kita akan menguji indikator Simple Moving Average (SMA). Library Backtesting.py telah menyediakan kode untuk mengkalkulasi indikator tersebut (baris ke-3). Namun, karena Backtesting.py merupakan library khusus untuk backtesting, jadi fitur untuk menghitung indikator yang tersedia hanya satu indikator tersebut.

stock_symbol = "BBCA.JK" 
start_date = "2021-01-01"
end_date = "2023-12-31"

data = yf.download(stock_symbol, start=start_date, end=end_date)

Baris-baris di atas berfungsi untuk mengunduh data historis perdagangan saham Bank BCA dari Yahoo Finance melalui library yfinance. Kita masukkan kode ticker serta tanggal awal dan akhir untuk data yang diinginkan. Dalam hal ini, saya memilih durasi selama 3 tahun. Data historis tersebut akan kita simpan dalam variabel data.

class SmaCross(Strategy):
    n1 = 10
    n2 = 20

    def init(self):
        close = self.data.Close
        self.sma1 = self.I(SMA, close, self.n1)
        self.sma2 = self.I(SMA, close, self.n2)

    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.sell()

Strategi yang akan kita uji, yaitu strategi crossover antar-indikator SMA, jika diterjemahkan ke dalam Python menggunakan Backtesting.py, maka akan terlihat seperti baris kode di atas yang kita buat dalam bentuk sebuah class dengan nama SmaCross.

bt = Backtest(data, SmaCross,
              cash=10000000, commission=.002,
              exclusive_orders=True)

output = bt.run()
bt.plot()

Kode di atas bertujuan untuk menginisiasi proses backtesting dengan menggunakan class Backtest. Ada beberapa parameter yang kita tambahkan:

Ada parameter-parameter lainnya yang bisa digunakan sesuai dengan kebutuhan strategi Anda. Untuk memahami dengan lebih menyeluruh, Anda bisa membaca dokumentasi dari library tersebut.

Kedua baris terakhir berfungsi untuk menjalankan proses backtesting yang hasilnya kita simpan dalam variabel `output` dan kemudian kita tampilkan dalam sebuah chart.

Gambar di atas adalah chart hasil dari eksekusi backtesting yang sudah dilakukan. Jika kita mencetak variabel `output`, maka akan tampil informasi detil backtesting yang sudah kita jalankan.

Start                     2021-01-04 00:00:00
End                       2023-12-29 00:00:00
Duration                   1089 days 00:00:00
Exposure Time [%]                   95.355191
Equity Final [$]                  10904438.25
Equity Peak [$]                   12530080.15
Return [%]                           9.044382
Buy & Hold Return [%]               37.527432
Return (Ann.) [%]                    3.025658
Volatility (Ann.) [%]                21.26367
Sharpe Ratio                         0.142292
Sortino Ratio                        0.203599
Calmar Ratio                         0.146955
Max. Drawdown [%]                  -20.589003
Avg. Drawdown [%]                   -4.558755
Max. Drawdown Duration      350 days 00:00:00
Avg. Drawdown Duration       61 days 00:00:00
# Trades                                   45
Win Rate [%]                        42.222222
Best Trade [%]                      16.149953
Worst Trade [%]                     -9.272364
Avg. Trade [%]                       0.192823
Max. Trade Duration         119 days 00:00:00
Avg. Trade Duration          24 days 00:00:00
Profit Factor                        1.216421
Expectancy [%]                       0.282923
SQN                                  0.295985
_strategy                            SmaCross
_equity_curve                             ...
_trades                       Size  EntryB..

Dari informasi di atas, dapat kita ketahui bahwa selama periode uji, strategi tersebut dieksekusi sebanyak 45 kali dengan win rate sebesar 42%. Di ujung periode, modal yang kita gunakan telah berkembang menjadi Rp10.904.438,- yang berarti strategi tersebut menghasilkan return sebesar 9,04%. Walaupun di akhir periode modal kita berkembang, tapi dengan return sebesar itu saya bisa katakan bahwa itu bukan merupakan return yang bagus 😁.

Anyway, yang kita uji barusan cuma strategi yang super simpel yang saya buat tanpa adanya analisis apapun sebelumnya. Jadi wajar saja jika return yang didapat tidak seperti yang diharapkan. Pertanyaannya, bagaimana kalau kita ingin menguji strategi lain, atau strategi yang lebih kompleks? Tadi sempat saya bilang, karena Backtesting.py ini merupakan library khusus backtesting, maka hanya ada satu perhitungan indikator dalam library tersebut, yaitu SMA. Itupun indikator itu ada karena untuk kepentingan demo semata.

Meskipun Backtesting.py menyatakan di dokumentasinya bahwa library tersebut bisa kompatibel dengan library untuk perhitungan indikator teknikal yang lain, seperti TA-lib maupun Tulip. Namun untuk menggunakan library tersebut memerlukan proses instalasi yang cukup rumit. Lantas apa solusinya? Ya, kita buat perhitungan indikator sendiri saja 😏.

Salah satu fleksibilitas yang ditawarkan oleh library Backtesting.py adalah kita dapat menambahkan sendiri indikator yang mau kita uji. Misalnya kita akan menambahkan indikator Exponential Moving Average, maka rumus perhitungan indikatornya kurang lebih akan seperti ini:

import pandas as pd

def EMA(data, periods):
    multiplier = 2 / (periods + 1)
    ema_values = [data[0]]

    for i in range(1, len(data)):
        ema = (data[i] - ema_values[-1]) * multiplier + ema_values[-1]
        ema_values.append(ema)

    return pd.Series(ema_values, name=f'EMA_{periods}')

Lalu untuk strateginya anggap saja kita menggunakan strategi yang sama dengan strategi sebelumnya, yaitu strategi crossover. Kita asumsikan periode short-term dan long-term-nya sama dengan strategi sebelumnya (EMA(10) dan EMA(20)) dan asumsi-asumsi lainnya sama seperti strategi SMA tadi. Maka kodenya kira-kira seperti ini:

from backtesting import Backtest, Strategy
from backtesting.lib import crossover
import yfinance as yf

stock_symbol = "BBCA.JK"
start_date = "2021-01-01"
end_date = "2023-12-31"

data = yf.download(stock_symbol, start=start_date, end=end_date)

class EmaCross(Strategy):
    n1 = 9
    n2 = 21

    def init(self):
        close = self.data.Close
        self.ema1 = self.I(EMA, close, self.n1)
        self.ema2 = self.I(EMA, close, self.n2)

    def next(self):
        if crossover(self.ema1, self.ema2):
            self.buy()
        elif crossover(self.ema2, self.ema1):
            self.sell()

bt = Backtest(data, EmaCross,
              cash=10000000, commission=.002,
              exclusive_orders=True)

output = bt.run()
bt.plot()

Jika kita eksekusi kode di atas, maka hasilnya seperti di bawah ini:

Detil output proses backtesting itu sendiri ditampilkan sebagai berikut:

Start                     2021-01-04 00:00:00
End                       2023-12-29 00:00:00
Duration                   1089 days 00:00:00
Exposure Time [%]                   97.540984
Equity Final [$]                   7190769.88
Equity Peak [$]                   10916983.68
Return [%]                         -28.092301
Buy & Hold Return [%]               37.527432
Return (Ann.) [%]                  -10.732542
Volatility (Ann.) [%]               19.120835
Sharpe Ratio                              0.0
Sortino Ratio                             0.0
Calmar Ratio                              0.0
Max. Drawdown [%]                  -35.768974
Avg. Drawdown [%]                   -12.48906
Max. Drawdown Duration      470 days 00:00:00
Avg. Drawdown Duration      152 days 00:00:00
# Trades                                   36
Win Rate [%]                        27.777778
Best Trade [%]                      14.158476
Worst Trade [%]                     -6.997151
Avg. Trade [%]                      -0.912328
Max. Trade Duration         115 days 00:00:00
Avg. Trade Duration          30 days 00:00:00
Profit Factor                        0.610207
Expectancy [%]                      -0.820571
SQN                                 -1.170089
_strategy                            EmaCross
_equity_curve                             ...
_trades                       Size  EntryB...
dtype: object

Seperti yang dilihat, dengan menggunakan indikator EMA, kita akan meraih win rate sebesar 27% dan pada akhir periode modal kita menyusut menjadi Rp7.190.770, alias return -28,1%. Hasil ini malah lebih buruk dari strategi SMA tadi 🥴.

But don’t get me wrong, tulisan ini dibuat bukan untuk membandingkan keampuhan antar indikator. Namun lebih ke pengenalan proses backtesting dan cara mengerjakannya dengan library Backtesting.py. Semua indikator, baik yang teknikal maupun yang fundamental ada plus dan minusnya. Indikator yang tepat, dengan parameter yang tepat, diimplementasikan dengan strategi yang tepat, dan pada saham yang tepat, jelas dapat memberikan keuntungan yang maksimal. Tinggal bagaimana kita sebagai investor ataupun trader yang rajin melakukan riset dan analisis yang mendalam 🤓.

Oke, saya rasa tulisan ini sudah terlalu panjang. Buat teman-teman yang ingin belajar lebih lanjut tentang backtesting dengan library Backtesting.py ini, saya sudah sediakan sebuah notebook di Google Colab yang bisa ditiru dan dipelajari lebih lanjut. Di notebook tersebut saya juga sudah tambahkan satu indikator lagi, yaitu Relative Strength Index (RSI) untuk teman-teman eksplorasi lagi. Notebook tersebut kemungkinan akan saya perbarui lagi dengan indikator-indikator yang lain. Jika teman-teman menemukan kesalahan dalam tulisan ini atau ingin dibuatkan indikator lain yang spesifik, bisa disampaikan lewat kolom komentar di bawah.

Stay hungry, stay foolish. Sampai jumpa di tulisan berikutnya.

Exit mobile version