Test Trading Performance Based on MACD (Python)
November 3, 2020Note
- Use MACD signal only: Buy when Signal line cross above MACD line (Postive Histogram), Sell when Signal line cross below MACD line (Negative Histogram)
- Plot candle chart and MACD chart for better visualization
- Plot buy and sell points on the chart
- Print buy/sell transactions and profit/loss
- Meassure performance
NOTE: The Best Times to Use the MACD Indicator
Code
Install libraries
pip install yfinance
pip install mplfinance
Refer Install Python TA-Lib (Ubuntu)
import yfinance as yf
import mplfinance as mpf
import talib as ta
import numpy as np
import pandas as pd
import math
ticker_name = 'MSFT'
yticker = yf.Ticker(ticker_name)
data = yticker.history(period="1y") # max, 1y, 3mo
# macd
data["macd"], data["macd_signal"], data["macd_hist"] = ta.MACD(data['Close'])
# plot macd
macd_plot = mpf.make_addplot(data["macd"], panel=1, color='fuchsia', title="MACD")
colors = ['g' if v >= 0 else 'r' for v in data["macd_hist"]]
macd_hist_plot = mpf.make_addplot(data["macd_hist"], type='bar', panel=1, color=colors) # color='dimgray'
macd_signal_plot = mpf.make_addplot(data["macd_signal"], panel=1, color='b')
# buy/sell
buy_signals, sell_signals, signals = detect_macd_signals(data)
# plot buy/sell
buy_plot = mpf.make_addplot(buy_signals, type='scatter', marker='v', markersize=100, panel=0)
sell_plot = mpf.make_addplot(sell_signals, type='scatter', marker='^', markersize=100, panel=0)
# print buy/sell transaction and stats
print_performance_summary(signals)
# plot candle chart and all
plots = [macd_plot, macd_signal_plot, macd_hist_plot, buy_plot, sell_plot]
mpf.plot(data, type='candle', style='yahoo', addplot=plots, title=f"\n{ticker_name}", ylabel='')
def detect_macd_signals(data):
"""Use MACD cross-over to decide buy/sell
Args:
data: panda DataFrame with OHLC with MACD data
Return:
buy_signals, sell_signals: for chart plot
signals: buy/sell transaction for summary printing
"""
buy_signals = [np.nan]
sell_signals = [np.nan]
signals = []
last_signal = None
for i in range(1, len(data)):
if data['macd_hist'][i-1] < 0 and data['macd_hist'][i] > 0:
price = (data['Open'][i] + data['Close'][i]) / 2
buy_signals.append(price)
last_signal = 'buy'
signals.append({
'date': data.index[i],
'action': 'buy',
'price': price
})
sell_signals.append(np.nan)
elif data['macd_hist'][i-1] > 0 and data['macd_hist'][i] < 0 and last_signal == 'buy':
price = (data['Open'][i] + data['Close'][i]) / 2
sell_signals.append(price)
last_signal = 'sell'
signals.append({
'date': data.index[i],
'action': 'sell',
'price': price
})
buy_signals.append(np.nan)
else:
buy_signals.append(np.nan)
sell_signals.append(np.nan)
return buy_signals, sell_signals, signals
def print_performance_summary(signals):
"""Print buy/sell transactions and statistics
Args:
signals: recorded buy/sell transactions
"""
pairs = zip(*[iter(signals)]*2)
rows = []
profit_count = 0
profit_pct_avg = 0
for (buy, sell) in pairs:
profit = sell['price'] - buy['price']
profit_pct = profit / buy['price']
if profit > 0:
profit_count += 1
profit_pct_avg += profit_pct
row = {
'buy_date': buy['date'],
'duration': (sell['date'] - buy['date']).days,
'profit': profit,
'profit_pct': "{0:.2%}".format(profit_pct)
}
rows.append(row)
df = pd.DataFrame(rows, columns=['buy_date', 'duration', 'profit', 'profit_pct'])
with pd.option_context('display.max_rows', None, 'display.max_columns', None):
print(df)
total_transaction = math.floor(len(signals) / 2)
stats = {
'total_transaction': total_transaction,
'profit_rate': "{0:.2%}".format(profit_count / total_transaction),
'avg_profit_per_transaction': "{0:.2%}".format(profit_pct_avg / total_transaction)
}
for key, value in stats.items():
print('{0:30} {1}'.format(key, value))
Result
Output of print_performance_summary
for MSFT
- We manage to do 8 pairs of trade (buy/sell) in 1 year
- 5/8 (62.50%) trades are profitable
- Average profit per trade is 3.89%, with most profitable trade making 18.98%, while the worst lost -3.96%.
buy_date duration profit profit_pct
0 2020-01-09 18 -0.248023 -0.15%
1 2020-01-30 22 7.896236 4.59%
2 2020-03-26 48 28.745260 18.98%
3 2020-06-09 6 -2.134837 -1.13%
4 2020-06-16 29 15.532458 8.06%
5 2020-08-04 7 -8.439593 -3.96%
6 2020-08-20 15 2.615005 1.23%
7 2020-09-29 24 7.324997 3.52%
total_transaction 8
profit_rate 62.50%
avg_profit_per_transaction 3.89%
What’s Next
- We assume the cross-over of MACD and Signal line is the buy/sell trigger. We could run more tests to determine whether delaying N day after cross-over would yield better result.
- Maybe we want to consider the weightage if the signal line is above 0 or below 0.
- We should test with more stocks, either S&P 500 or NASDAQ 100.
- We should experiment of using MACD together with other signals, such as SMA or Stochastic/RSI.
❤️ Is this article helpful?
Buy me a coffee☕ or support my work to keep this space 🖖 and ad-free.
If you can't, do send some 💖 to @d_luaz or help to share this article.
If you can't, do send some 💖 to @d_luaz or help to share this article.
👶 Apps I built
Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.Pixtory App (Alpha) - easily organize photos on your phone into a blog.
暖心芽 (WIP) 🌞❤️🌱 - reminder of hope, warmth, thoughts and feelings (or just quotes).
LuaPass - offline password manager
By Desmond Lua
- algo-trading
- algolia
- analytics
- android
- android-ktx
- android-permission
- android-studio
- apps-script
- bash
- binance
- bootstrap
- bootstrapvue
- chartjs
- chrome
- cloud-functions
- coding-interview
- contentresolver
- coroutines
- crashlytics
- crypto
- css
- dagger2
- datastore
- datetime
- docker
- eslint
- firebase
- firebase-auth
- firebase-hosting
- firestore
- firestore-security-rules
- flask
- fontawesome
- fresco
- git
- github
- glide
- godot
- google-app-engine
- google-cloud-storage
- google-colab
- google-drive
- google-maps
- google-places
- google-play
- google-sheets
- gradle
- html
- hugo
- inkscape
- java
- java-time
- javascript
- jetpack-compose
- jetson-nano
- kotlin
- kotlin-serialization
- layout
- lets-encrypt
- lifecycle
- linux
- logging
- lubuntu
- markdown
- mate
- material-design
- matplotlib
- md5
- mongodb
- moshi
- mplfinance
- mysql
- navigation
- nginx
- nodejs
- npm
- nuxtjs
- nvm
- pandas
- payment
- pip
- pwa
- pyenv
- python
- recylerview
- regex
- room
- rxjava
- scoped-storage
- selenium
- social-media
- ssh
- ssl
- static-site-generator
- static-website-hosting
- sublime-text
- ubuntu
- unit-test
- uwsgi
- viewmodel
- viewpager2
- virtualbox
- vue-chartjs
- vue-cli
- vue-router
- vuejs
- vuelidate
- vuepress
- web-development
- web-hosting
- webpack
- windows
- workmanager
- wsl
- yarn