sell-short-in-may-and-go-away

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

algo - Sell short in May and go away, buy to cover in Nov
algo2 - first trading day of the month, adjust position to 50%
(select the one you want to call in the Strategy.run() function


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(2015, 10, 30)
#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 = self._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 to cover (at the open on first trading day in Nov)
            if (self._tlog.shares > 0
                and row.month == 11 and row.first_dotm or end_flag):
                shares = self._tlog.buy2cover(date, row.open)

            # sell short (at the open on first trading day in May)
            elif (self._tlog.shares == 0
                  and row.month == 5 and row.first_dotm):
                shares = self._tlog.sell_short(date, row.open)

            if shares > 0:
                pf.DBG("{0} SELL SHORT  {1} {2} @ {3:.2f}".format(
                       date, shares, self._symbol, row.open))
            elif shares < 0:
                pf.DBG("{0} BUY TO COVER {1} {2} @ {3:.2f}".format(
                       date, -shares, self._symbol, row.open))
            # record daily balance
            self._dbal.append(date, high, low, close)

    def _algo2(self):
        pf.TradeLog.cash = self._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

            # on the first day of the month, adjust short position to 50%
            if (row.first_dotm or end_flag):
                weight = 0 if end_flag else 0.5
                self._tlog.adjust_percent(date, close, weight, pf.Direction.SHORT)

            # 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(self._symbol)
        self._dbal = pf.DailyBal()
        
        self._ts, self._start = pf.finalize_timeseries(self._ts, self._start)

        self._algo()
        #self._algo2()

    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.head(10)


Out[8]:
date price shares entry_exit direction symbol
0 2016-05-02 190.40 52 entry SHRT SPY
1 2016-11-01 197.95 52 exit SHRT SPY
2 2017-05-01 224.16 42 entry SHRT SPY
3 2017-11-01 244.74 42 exit SHRT SPY
4 2018-05-01 252.55 34 entry SHRT SPY
5 2018-11-01 262.30 34 exit SHRT SPY
6 2019-05-01 287.55 29 entry SHRT SPY
7 2019-11-01 300.33 29 exit SHRT SPY
8 2020-05-01 284.06 28 entry SHRT SPY
9 2020-07-15 322.41 28 exit SHRT SPY

In [9]:
tlog.head(10)


Out[9]:
entry_date entry_price exit_date exit_price pl_points pl_cash qty cumul_total direction symbol
0 2016-05-02 190.40 2016-11-01 197.95 -7.54 -392.29 52 -392.29 SHRT SPY
1 2017-05-01 224.16 2017-11-01 244.74 -20.57 -864.07 42 -1256.36 SHRT SPY
2 2018-05-01 252.55 2018-11-01 262.30 -9.75 -331.43 34 -1587.79 SHRT SPY
3 2019-05-01 287.55 2019-11-01 300.33 -12.78 -370.61 29 -1958.39 SHRT SPY
4 2020-05-01 284.06 2020-07-15 322.41 -38.35 -1073.80 28 -3032.19 SHRT SPY

In [10]:
dbal.tail(10)


Out[10]:
high low close shares cash leverage state
date
2020-07-01 7341.33 7262.37 7300.73 28 87.93 1.00 -
2020-07-02 7273.01 7155.69 7252.85 28 87.93 1.00 -
2020-07-06 7159.61 7100.25 7117.89 28 87.93 1.00 -
2020-07-07 7220.93 7104.73 7209.45 28 87.93 1.00 -
2020-07-08 7239.69 7138.89 7142.25 28 87.93 1.00 -
2020-07-09 7296.25 7116.49 7192.65 28 87.93 1.00 -
2020-07-10 7238.01 7094.65 7102.77 28 87.93 1.00 -
2020-07-13 7199.65 6959.41 7179.77 28 87.93 1.00 -
2020-07-14 7259.29 7042.01 7065.53 28 87.93 1.00 -
2020-07-15 6967.81 6967.81 6967.81 0 6967.81 1.00 X

In [11]:
pf.print_full(stats)


start                                                  2015-10-30
end                                                    2020-07-15
beginning_balance                                           10000
ending_balance                                            6967.81
total_net_profit                                         -3032.19
gross_profit                                                 0.00
gross_loss                                               -3032.19
profit_factor                                                   0
return_on_initial_capital                                  -30.32
annual_return_rate                                          -7.38
trading_period                           4 years 8 months 15 days
pct_time_in_market                                          47.89
margin                                                          1
avg_leverage                                                 1.00
max_leverage                                                 1.00
min_leverage                                                 1.00
total_num_trades                                                5
trades_per_year                                              1.06
num_winning_trades                                              0
num_losing_trades                                               5
num_even_trades                                                 0
pct_profitable_trades                                        0.00
avg_profit_per_trade                                      -606.44
avg_profit_per_winning_trade                                    0
avg_loss_per_losing_trade                                 -606.44
ratio_avg_profit_win_loss                                       0
largest_profit_winning_trade                                    0
largest_loss_losing_trade                                -1073.80
num_winning_points                                              0
num_losing_points                                          -88.99
total_net_points                                           -88.99
avg_points                                                 -17.80
largest_points_winning_trade                                    0
largest_points_losing_trade                                -38.35
avg_pct_gain_per_trade                                      -6.99
largest_pct_winning_trade                                       0
largest_pct_losing_trade                                   -13.50
max_consecutive_winning_trades                                  0
max_consecutive_losing_trades                                   5
avg_bars_winning_trades                                         0
avg_bars_losing_trades                                     114.20
max_closed_out_drawdown                                    -32.36
max_closed_out_drawdown_start_date                     2016-06-27
max_closed_out_drawdown_end_date                       2020-07-15
max_closed_out_drawdown_recovery_date           Not Recovered Yet
drawdown_recovery                                           -4.05
drawdown_annualized_return                                   4.38
max_intra_day_drawdown                                     -32.74
avg_yearly_closed_out_drawdown                              -9.04
max_yearly_closed_out_drawdown                             -19.74
avg_monthly_closed_out_drawdown                             -1.73
max_monthly_closed_out_drawdown                            -14.24
avg_weekly_closed_out_drawdown                              -0.66
max_weekly_closed_out_drawdown                              -6.58
avg_yearly_closed_out_runup                                  6.07
max_yearly_closed_out_runup                                 16.14
avg_monthly_closed_out_runup                                 1.41
max_monthly_closed_out_runup                                11.81
avg_weekly_closed_out_runup                                  0.55
max_weekly_closed_out_runup                                  9.02
pct_profitable_years                                        11.16
best_year                                                    9.96
worst_year                                                 -18.89
avg_year                                                    -5.24
annual_std                                                   4.90
pct_profitable_months                                       17.61
best_month                                                  11.81
worst_month                                                -11.26
avg_month                                                   -0.52
monthly_std                                                  2.20
pct_profitable_weeks                                        18.83
best_week                                                    7.25
worst_week                                                  -6.58
avg_week                                                    -0.14
weekly_std                                                   1.19
sharpe_ratio                                                -0.76
sortino_ratio                                               -0.90
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()


2015-10-30 00:00:00 BUY  48 SPY @ 207.93
2020-07-15 00:00:00 SELL 48 SPY @ 321.85

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 -7.38 9.71
max_closed_out_drawdown -32.36 -34.06
drawdown_annualized_return 4.38 -3.51
drawdown_recovery -4.05 -0.09
best_month 11.81 23.03
worst_month -11.26 -31.35
sharpe_ratio -0.76 0.58
sortino_ratio -0.90 0.64
monthly_std 2.20 4.78