Golden Cross / Death Cross S&P 500 index (^GSPC)¶
1. sma50>sma200, buy
2. sma50<sma200, sell your long position.
In [1]:
import datetime
import matplotlib.pyplot as plt
import pandas as pd
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)
Some global data
In [3]:
symbol = '^GSPC'
#symbol = 'SPY'
capital = 10000
start = datetime.datetime(1900, 1, 1)
#start = datetime.datetime(*pf.SP500_BEGIN)
end = datetime.datetime.now()
use_adj=True
Prepare timeseries
In [4]:
# Fetch and select timeseries.
ts = pf.fetch_timeseries(symbol,)
ts = pf.select_tradeperiod(ts, start, end, use_adj=use_adj)
# Add technical indicator: day sma regime filter.
ts['regime'] = \
pf.CROSSOVER(ts, timeperiod_fast=50, timeperiod_slow=200)
# Finalize the time series before implementing trading strategy.
ts, start = pf.finalize_timeseries(ts, start, dropna=True, drop_columns=['open', 'high', 'low'])
# Create Trade Log (tlog); Create Daily Balance (dbal).
tlog = pf.TradeLog(symbol)
dbal = pf.DailyBal()
In [5]:
# Yahoo S&P 500 timeseries has issues, specifically some of the intraday value are 0.
# drop_columns removes these, then dropna drops the first 200 rows with regime value
# that are NA. pf.find_nan_rows() can help diagnose these issues.
df = pf.find_nan_rows(ts)
df
Out[5]:
close | adj_close | volume | regime | |
---|---|---|---|---|
date |
Algo: Buy when 50 day ma crosses above 200 day ma. Sell when 50 day ma crosses below 200 day ma.
In [6]:
pf.TradeLog.cash = capital
for i, row in enumerate(ts.itertuples()):
date = row.Index.to_pydatetime()
end_flag = pf.is_last_row(ts, i)
# Buy
# Note ts['regime'][i-1] is regime for previous day.
# We want to buy only on the day of a moving average crossover.
# i.e. yesteraday regime is negative, today it is positive.
if tlog.shares == 0:
if row.regime > 0 and ts['regime'].iloc[i-1] < 0:
tlog.buy(date, row.close)
# Sell
else:
if row.regime < 0 or end_flag:
tlog.sell(date, row.close)
# Record daily balance.
dbal.append(date, row.close)
Retrieve logs
In [7]:
tlog = tlog.get_log()
dbal = dbal.get_log(tlog)
View trade log
In [8]:
tlog.head(50)
Out[8]:
entry_date | entry_price | exit_date | exit_price | pl_points | pl_cash | qty | cumul_total | direction | symbol | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1932-09-19 | 7.34 | 1933-03-27 | 6.09 | -1.25 | -1702.50 | 1362 | -1702.50 | LONG | ^GSPC |
1 | 1933-05-18 | 8.89 | 1934-05-31 | 9.61 | 0.72 | 671.76 | 933 | -1030.74 | LONG | ^GSPC |
2 | 1935-05-23 | 10.07 | 1937-05-21 | 16.27 | 6.20 | 5518.00 | 890 | 4487.26 | LONG | ^GSPC |
3 | 1938-07-27 | 12.25 | 1939-03-31 | 10.98 | -1.27 | -1501.14 | 1182 | 2986.12 | LONG | ^GSPC |
4 | 1939-09-18 | 12.47 | 1940-03-20 | 12.14 | -0.33 | -343.53 | 1041 | 2642.59 | LONG | ^GSPC |
5 | 1940-12-13 | 10.69 | 1941-02-21 | 9.76 | -0.93 | -1099.26 | 1182 | 1543.33 | LONG | ^GSPC |
6 | 1941-08-18 | 10.13 | 1941-11-18 | 9.26 | -0.87 | -990.93 | 1139 | 552.40 | LONG | ^GSPC |
7 | 1942-08-14 | 8.58 | 1943-12-01 | 11.13 | 2.55 | 3133.95 | 1229 | 3686.35 | LONG | ^GSPC |
8 | 1944-03-13 | 12.24 | 1946-08-28 | 16.73 | 4.49 | 5019.82 | 1118 | 8706.17 | LONG | ^GSPC |
9 | 1947-07-25 | 16.08 | 1948-01-22 | 14.42 | -1.66 | -1930.58 | 1163 | 6775.59 | LONG | ^GSPC |
10 | 1948-05-14 | 16.39 | 1948-12-01 | 15.01 | -1.38 | -1411.74 | 1023 | 5363.85 | LONG | ^GSPC |
11 | 1949-08-30 | 15.21 | 1953-05-11 | 24.91 | 9.70 | 9797.00 | 1010 | 15160.85 | LONG | ^GSPC |
12 | 1953-12-21 | 24.95 | 1956-10-26 | 46.27 | 21.32 | 21490.56 | 1008 | 36651.41 | LONG | ^GSPC |
13 | 1957-06-03 | 47.37 | 1957-09-26 | 42.57 | -4.80 | -4723.20 | 984 | 31928.21 | LONG | ^GSPC |
14 | 1958-05-08 | 43.99 | 1959-10-30 | 57.52 | 13.53 | 12894.09 | 953 | 44822.30 | LONG | ^GSPC |
15 | 1959-12-30 | 59.77 | 1960-02-15 | 55.17 | -4.60 | -4218.20 | 917 | 40604.10 | LONG | ^GSPC |
16 | 1961-01-04 | 58.36 | 1962-05-07 | 66.02 | 7.66 | 6641.22 | 867 | 47245.32 | LONG | ^GSPC |
17 | 1963-01-03 | 63.72 | 1965-07-22 | 83.85 | 20.13 | 18076.74 | 898 | 65322.05 | LONG | ^GSPC |
18 | 1965-09-17 | 90.05 | 1966-04-28 | 91.13 | 1.08 | 902.88 | 836 | 66224.93 | LONG | ^GSPC |
19 | 1967-02-03 | 87.36 | 1968-02-27 | 90.53 | 3.17 | 2764.24 | 872 | 68989.17 | LONG | ^GSPC |
20 | 1968-05-17 | 96.90 | 1969-03-13 | 98.39 | 1.49 | 1214.35 | 815 | 70203.51 | LONG | ^GSPC |
21 | 1969-05-27 | 103.57 | 1969-06-23 | 96.23 | -7.34 | -5681.16 | 774 | 64522.36 | LONG | ^GSPC |
22 | 1970-10-22 | 83.38 | 1971-09-24 | 98.15 | 14.77 | 13189.61 | 893 | 77711.97 | LONG | ^GSPC |
23 | 1972-01-26 | 102.50 | 1973-04-18 | 111.54 | 9.04 | 7729.20 | 855 | 85441.17 | LONG | ^GSPC |
24 | 1975-03-06 | 83.69 | 1976-12-01 | 102.49 | 18.80 | 21431.99 | 1140 | 106873.17 | LONG | ^GSPC |
25 | 1977-01-04 | 105.70 | 1977-03-03 | 100.88 | -4.82 | -5326.10 | 1105 | 101547.07 | LONG | ^GSPC |
26 | 1978-05-22 | 99.09 | 1978-12-13 | 96.06 | -3.03 | -3408.75 | 1125 | 98138.32 | LONG | ^GSPC |
27 | 1979-03-21 | 101.25 | 1980-04-22 | 103.43 | 2.18 | 2328.24 | 1068 | 100466.56 | LONG | ^GSPC |
28 | 1980-06-17 | 116.03 | 1981-07-02 | 128.64 | 12.61 | 12004.72 | 952 | 112471.28 | LONG | ^GSPC |
29 | 1982-09-28 | 123.24 | 1984-02-03 | 160.91 | 37.67 | 37406.32 | 993 | 149877.59 | LONG | ^GSPC |
30 | 1984-09-12 | 164.68 | 1986-11-18 | 236.78 | 72.10 | 69937.01 | 970 | 219814.60 | LONG | ^GSPC |
31 | 1986-11-25 | 248.17 | 1987-11-05 | 254.48 | 6.31 | 5843.06 | 926 | 225657.66 | LONG | ^GSPC |
32 | 1988-06-28 | 272.31 | 1990-02-26 | 328.67 | 56.36 | 48751.41 | 865 | 274409.07 | LONG | ^GSPC |
33 | 1990-05-25 | 354.58 | 1990-09-07 | 323.40 | -31.18 | -25006.35 | 802 | 249402.72 | LONG | ^GSPC |
34 | 1991-02-15 | 369.06 | 1994-04-19 | 442.54 | 73.48 | 51582.97 | 702 | 300985.69 | LONG | ^GSPC |
35 | 1994-09-15 | 474.81 | 1998-09-29 | 1049.02 | 574.21 | 375533.35 | 654 | 676519.04 | LONG | ^GSPC |
36 | 1998-12-08 | 1181.38 | 1999-11-04 | 1362.64 | 181.26 | 105312.07 | 581 | 781831.11 | LONG | ^GSPC |
37 | 1999-11-11 | 1381.46 | 2000-10-30 | 1398.66 | 17.20 | 9855.64 | 573 | 791686.75 | LONG | ^GSPC |
38 | 2003-05-14 | 939.28 | 2004-08-18 | 1095.17 | 155.89 | 132974.18 | 853 | 924660.93 | LONG | ^GSPC |
39 | 2004-11-05 | 1166.17 | 2006-07-19 | 1259.81 | 93.64 | 75005.65 | 801 | 999666.58 | LONG | ^GSPC |
40 | 2006-09-12 | 1313.00 | 2007-12-21 | 1484.46 | 171.46 | 131681.25 | 768 | 1131347.83 | LONG | ^GSPC |
41 | 2009-06-23 | 895.10 | 2010-07-02 | 1022.58 | 127.48 | 162537.05 | 1275 | 1293884.89 | LONG | ^GSPC |
42 | 2010-10-22 | 1183.08 | 2011-08-12 | 1178.81 | -4.27 | -4705.43 | 1102 | 1289179.46 | LONG | ^GSPC |
43 | 2012-01-31 | 1312.41 | 2015-08-28 | 1988.87 | 676.46 | 669018.90 | 989 | 1958198.36 | LONG | ^GSPC |
44 | 2015-12-21 | 2021.15 | 2016-01-11 | 1923.67 | -97.48 | -94848.02 | 973 | 1863350.34 | LONG | ^GSPC |
45 | 2016-04-25 | 2087.79 | 2018-12-07 | 2633.08 | 545.29 | 489125.17 | 897 | 2352475.50 | LONG | ^GSPC |
46 | 2019-04-01 | 2867.19 | 2020-03-30 | 2626.65 | -240.54 | -197964.45 | 823 | 2154511.05 | LONG | ^GSPC |
47 | 2020-07-09 | 3152.05 | 2022-03-14 | 4173.11 | 1021.06 | 700447.03 | 686 | 2854958.08 | LONG | ^GSPC |
48 | 2023-02-02 | 4179.76 | 2023-12-21 | 4732.65 | 552.89 | 378729.74 | 685 | 3233687.83 | LONG | ^GSPC |
Generate strategy stats - display all available stats
In [9]:
stats = pf.stats(ts, tlog, dbal, capital)
pf.print_full(stats)
start 1928-10-16 end 2023-12-21 beginning_balance 10000 ending_balance 3243687.83 total_net_profit 3233687.83 gross_profit 3588549.17 gross_loss -354861.34 profit_factor 10.11 return_on_initial_capital 32336.88 annual_return_rate 6.26 trading_period 95 years 2 months 5 days pct_time_in_market 66.17 margin 1 avg_leverage 1.00 max_leverage 1.00 min_leverage 1.00 total_num_trades 49 trades_per_year 0.51 num_winning_trades 33 num_losing_trades 16 num_even_trades 0 pct_profitable_trades 67.35 avg_profit_per_trade 65993.63 avg_profit_per_winning_trade 108743.91 avg_loss_per_losing_trade -22178.83 ratio_avg_profit_win_loss 4.90 largest_profit_winning_trade 700447.03 largest_loss_losing_trade -197964.45 num_winning_points 4512.20 num_losing_points -405.75 total_net_points 4106.45 avg_points 83.81 largest_points_winning_trade 1021.06 largest_points_losing_trade -240.54 avg_pct_gain_per_trade 15.07 largest_pct_winning_trade 120.93 largest_pct_losing_trade -17.03 expected_shortfall 0 max_consecutive_winning_trades 8 max_consecutive_losing_trades 4 avg_bars_winning_trades 430.85 avg_bars_losing_trades 103.19 max_closed_out_drawdown -36.86 max_closed_out_drawdown_peak_date 1937-03-10 max_closed_out_drawdown_trough_date 1941-11-13 max_closed_out_drawdown_recovery_date 1945-05-03 drawdown_loss_period 1709 drawdown_recovery_period 1267 annualized_return_over_max_drawdown 0.17 max_intra_day_drawdown -36.86 avg_yearly_closed_out_drawdown -9.55 max_yearly_closed_out_drawdown -35.09 avg_monthly_closed_out_drawdown -2.33 max_monthly_closed_out_drawdown -31.47 avg_weekly_closed_out_drawdown -0.89 max_weekly_closed_out_drawdown -28.51 avg_yearly_closed_out_runup 16.79 max_yearly_closed_out_runup 61.85 avg_monthly_closed_out_runup 2.84 max_monthly_closed_out_runup 26.55 avg_weekly_closed_out_runup 1.03 max_weekly_closed_out_runup 23.14 pct_profitable_years 61.16 best_year 53.98 worst_year -28.66 avg_year 7.01 annual_std 13.04 pct_profitable_months 43.28 best_month 22.49 worst_month -30.91 avg_month 0.54 monthly_std 3.29 pct_profitable_weeks 38.68 best_week 23.14 worst_week -27.33 avg_week 0.14 weekly_std 1.75 pct_profitable_days 35.43 best_day 16.60 worst_day -20.47 avg_day 0.03 daily_std 0.81 sharpe_ratio 0.54 sharpe_ratio_max 0.56 sharpe_ratio_min 0.52 sortino_ratio 0.54 dtype: object
Benchmark: Run, retrieve logs, generate stats
In [10]:
benchmark = pf.Benchmark(symbol, capital, start, end, use_adj=use_adj)
benchmark.run()
Plot Equity Curves: Strategy vs Benchmark
In [11]:
pf.plot_equity_curve(dbal, benchmark=benchmark.dbal)
Plot Trades
In [12]:
pf.plot_trades(dbal, benchmark=benchmark.dbal)
Strategy vs Benchmark
In [13]:
df = pf.summary(stats, benchmark.stats, metrics=pf.currency_metrics)
df
Out[13]:
strategy | benchmark | |
---|---|---|
beginning_balance | $10,000.00 | $10,000.00 |
ending_balance | $3,243,687.83 | $2,181,752.85 |
total_net_profit | $3,233,687.83 | $2,171,752.85 |
gross_profit | $3,588,549.17 | $2,171,752.85 |
gross_loss | -$354,861.34 | $0.00 |
In [14]:
extras = ('avg_month',)
df = pf.plot_bar_graph(stats, benchmark.stats, extras=extras)
df
Out[14]:
strategy | benchmark | |
---|---|---|
annual_return_rate | 6.26 | 5.82 |
max_closed_out_drawdown | -36.86 | -86.16 |
annualized_return_over_max_drawdown | 0.17 | 0.07 |
best_month | 22.49 | 61.49 |
worst_month | -30.91 | -42.17 |
sharpe_ratio | 0.54 | 0.39 |
sortino_ratio | 0.54 | 0.49 |
monthly_std | 3.29 | 5.33 |
annual_std | 13.04 | 19.93 |
avg_month | 0.54 | 0.59 |
In [ ]: