sell-in-may-and-go-away

see: https://en.wikipedia.org/wiki/Sell_in_May

"Sell in May and go away" (aka the Halloween indicator) is an investment adage warning investors to divest their stock holdings in May and wait to reinvest in November.


In [1]:
import pandas as pd
import matplotlib.pyplot as plt
import datetime
from talib.abstract import *

import pinkfish as pf

# format price data
pd.options.display.float_format = '{:0.2f}'.format

%matplotlib inline

In [2]:
# set size of inline plots
'''note: rcParams can't be in same cell as import matplotlib
   or %matplotlib inline
   
   %matplotlib notebook: will lead to interactive plots embedded within
   the notebook, you can zoom and resize the figure
   
   %matplotlib inline: only draw static images in the notebook
'''
plt.rcParams["figure.figsize"] = (10, 7)

In [3]:
pf.DEBUG = False

Some global data


In [4]:
symbol = '^GSPC'
#symbol = 'SPY'
capital = 10000
start = datetime.datetime(1900, 1, 1)
#start = datetime.datetime.strptime(pf.SP500_BEGIN, '%Y-%m-%d')
end = datetime.datetime.now()

Define Strategy Class


In [5]:
class Strategy:

    def __init__(self, symbol, capital, start, end):
        self._symbol = symbol
        self._capital = capital
        self._start = start
        self._end = end

    def _algo(self):
        pf.TradeLog.cash = capital

        for i, row in enumerate(self._ts.itertuples()):

            date = row.Index.to_pydatetime()
            high = row.high; low = row.low; close = row.close; 
            end_flag = pf.is_last_row(self._ts, i)
            shares = 0

            # buy (at the open on first trading day in Nov)
            if (self._tlog.shares == 0
                and row.month == 11 and row.first_dotm):
                # enter buy in trade log
                shares = self._tlog.buy(date, row.open)
            # sell
            elif (self._tlog.shares > 0
                  and row.month == 5 and row.first_dotm
                  or end_flag):
                # enter sell in trade log
                shares = self._tlog.sell(date, row.open)

            if shares > 0:
                pf.DBG("{0} BUY  {1} {2} @ {3:.2f}".format(
                       date, shares, self._symbol, row.open))
            elif shares < 0:
                pf.DBG("{0} SELL {1} {2} @ {3:.2f}".format(
                       date, -shares, self._symbol, row.open))

            # record daily balance
            self._dbal.append(date, high, low, close)

    def run(self):
        self._ts = pf.fetch_timeseries(self._symbol)
        self._ts = pf.select_tradeperiod(self._ts, self._start, self._end,
                                         use_adj=True)
        # add calendar columns
        self._ts = pf.calendar(self._ts)
        
        self._tlog = pf.TradeLog(symbol)
        self._dbal = pf.DailyBal()
        
        self._ts, self._start = pf.finalize_timeseries(self._ts, self._start)

        self._algo()

    def get_logs(self):
        """ return DataFrames """
        self.rlog = self._tlog.get_log_raw()
        self.tlog = self._tlog.get_log()
        self.dbal = self._dbal.get_log(self.tlog)
        return self.rlog, self.tlog, self.dbal

    def get_stats(self):
        stats = pf.stats(self._ts, self.tlog, self.dbal, self._capital)
        return stats

Run Strategy


In [6]:
s = Strategy(symbol, capital, start, end)
s.run()

Retrieve log DataFrames


In [7]:
rlog, tlog, dbal = s.get_logs()
stats = s.get_stats()

In [8]:
rlog.tail()


Out[8]:
date price shares entry_exit direction symbol
179 2018-05-01 2642.96 158 exit LONG ^GSPC
180 2018-11-01 2717.58 153 entry LONG ^GSPC
181 2019-05-01 2952.33 153 exit LONG ^GSPC
182 2019-11-01 3050.72 148 entry LONG ^GSPC
183 2020-05-01 2869.09 148 exit LONG ^GSPC

In [9]:
tlog.tail()


Out[9]:
entry_date entry_price exit_date exit_price pl_points pl_cash qty cumul_total direction symbol
87 2015-11-02 2080.76 2016-05-02 2067.17 -13.59 -2391.86 176 354635.54 LONG ^GSPC
88 2016-11-01 2128.68 2017-05-01 2388.50 259.82 44429.23 171 399064.77 LONG ^GSPC
89 2017-11-01 2583.21 2018-05-01 2642.96 59.75 9440.50 158 408505.27 LONG ^GSPC
90 2018-11-01 2717.58 2019-05-01 2952.33 234.75 35916.75 153 444422.02 LONG ^GSPC
91 2019-11-01 3050.72 2020-05-01 2869.09 -181.63 -26881.22 148 417540.80 LONG ^GSPC

In [10]:
dbal.tail()


Out[10]:
high low close shares cash leverage state
date
2020-07-13 427540.80 427540.80 427540.80 0 427540.80 1.00 -
2020-07-14 427540.80 427540.80 427540.80 0 427540.80 1.00 -
2020-07-15 427540.80 427540.80 427540.80 0 427540.80 1.00 -
2020-07-16 427540.80 427540.80 427540.80 0 427540.80 1.00 -
2020-07-17 427540.80 427540.80 427540.80 0 427540.80 1.00 -

In [11]:
pf.print_full(stats)


start                                                   1927-12-30
end                                                     2020-07-17
beginning_balance                                            10000
ending_balance                                           427540.80
total_net_profit                                         417540.80
gross_profit                                             555329.27
gross_loss                                              -137788.47
profit_factor                                                 4.03
return_on_initial_capital                                  4175.41
annual_return_rate                                            4.14
trading_period                           92 years 6 months 17 days
pct_time_in_market                                           48.77
margin                                                           1
avg_leverage                                                  1.00
max_leverage                                                  1.00
min_leverage                                                  1.00
total_num_trades                                                92
trades_per_year                                               0.99
num_winning_trades                                              65
num_losing_trades                                               27
num_even_trades                                                  0
pct_profitable_trades                                        70.65
avg_profit_per_trade                                       4538.49
avg_profit_per_winning_trade                               8543.53
avg_loss_per_losing_trade                                 -5103.28
ratio_avg_profit_win_loss                                     1.67
largest_profit_winning_trade                              44429.23
largest_loss_losing_trade                                -26881.22
num_winning_points                                         2957.46
num_losing_points                                          -737.98
total_net_points                                           2219.48
avg_points                                                   24.12
largest_points_winning_trade                                259.82
largest_points_losing_trade                                -181.63
avg_pct_gain_per_trade                                        4.98
largest_pct_winning_trade                                    24.86
largest_pct_losing_trade                                    -45.33
max_consecutive_winning_trades                                   7
max_consecutive_losing_trades                                    6
avg_bars_winning_trades                                     124.15
avg_bars_losing_trades                                      124.37
max_closed_out_drawdown                                     -71.86
max_closed_out_drawdown_start_date                      1930-04-10
max_closed_out_drawdown_end_date                        1942-04-28
max_closed_out_drawdown_recovery_date                   1959-12-01
drawdown_recovery                                           -12.05
drawdown_annualized_return                                  -17.35
max_intra_day_drawdown                                      -71.86
avg_yearly_closed_out_drawdown                              -10.53
max_yearly_closed_out_drawdown                              -52.23
avg_monthly_closed_out_drawdown                              -2.03
max_monthly_closed_out_drawdown                             -30.76
avg_weekly_closed_out_drawdown                               -0.76
max_weekly_closed_out_drawdown                              -18.12
avg_yearly_closed_out_runup                                  16.02
max_yearly_closed_out_runup                                  97.53
avg_monthly_closed_out_runup                                  2.43
max_monthly_closed_out_runup                                 44.09
avg_weekly_closed_out_runup                                   0.87
max_weekly_closed_out_runup                                  23.14
pct_profitable_years                                         70.56
best_year                                                    83.17
worst_year                                                  -49.45
avg_year                                                      5.06
annual_std                                                   12.60
pct_profitable_months                                        34.24
best_month                                                   44.09
worst_month                                                 -30.76
avg_month                                                     0.38
monthly_std                                                   3.45
pct_profitable_weeks                                         29.07
best_week                                                    23.14
worst_week                                                  -18.12
avg_week                                                      0.10
weekly_std                                                    1.76
sharpe_ratio                                                  0.38
sortino_ratio                                                 0.35
dtype: object

Run Benchmark, Retrieve benchmark logs, and Generate benchmark stats


In [12]:
benchmark = pf.Benchmark(symbol, capital, s._start, s._end)
benchmark.run()
benchmark.tlog, benchmark.dbal = benchmark.get_logs()
benchmark.stats = benchmark.get_stats()


1927-12-30 00:00:00 BUY  566 ^GSPC @ 17.66
2020-07-17 00:00:00 SELL 566 ^GSPC @ 3224.73

Plot Equity Curves: Strategy vs Benchmark


In [13]:
pf.plot_equity_curve(dbal, benchmark=benchmark.dbal)


Plot Trades


In [14]:
pf.plot_trades(dbal, benchmark=benchmark.dbal)


Bar Graph: Strategy vs Benchmark


In [15]:
df = pf.plot_bar_graph(stats, benchmark.stats)
df


Out[15]:
strategy benchmark
annual_return_rate 4.14 5.79
max_closed_out_drawdown -71.86 -86.17
drawdown_annualized_return -17.35 -14.89
drawdown_recovery -12.05 -2.71
best_month 44.09 61.42
worst_month -30.76 -42.18
sharpe_ratio 0.38 0.39
sortino_ratio 0.35 0.49
monthly_std 3.45 5.35