{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import vectorbt as vbt"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"split_kwargs = dict(\n",
" n=30, \n",
" window_len=365 * 2, \n",
" set_lens=(180,), \n",
" left_to_right=False\n",
") # 30 windows, each 2 years long, reserve 180 days for test\n",
"pf_kwargs = dict(\n",
" direction='both', # long and short\n",
" freq='d'\n",
")\n",
"windows = np.arange(10, 50)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"price = vbt.YFData.download('BTC-USD').get('Close')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Date\n",
"2014-09-17 00:00:00+00:00 457.334015\n",
"2014-09-18 00:00:00+00:00 424.440002\n",
"2014-09-19 00:00:00+00:00 394.795990\n",
"2014-09-20 00:00:00+00:00 408.903992\n",
"2014-09-21 00:00:00+00:00 398.821014\n",
" ... \n",
"2021-08-21 00:00:00+00:00 48905.492188\n",
"2021-08-22 00:00:00+00:00 49321.652344\n",
"2021-08-23 00:00:00+00:00 49546.148438\n",
"2021-08-24 00:00:00+00:00 47706.117188\n",
"2021-08-25 00:00:00+00:00 47449.765625\n",
"Name: Close, Length: 2531, dtype: float64\n"
]
}
],
"source": [
"print(price)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"price.vbt.plot().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def roll_in_and_out_samples(price, **kwargs):\n",
" return price.vbt.rolling_split(**kwargs)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"roll_in_and_out_samples(price, **split_kwargs, plot=True, trace_names=['in-sample', 'out-sample']).show_svg()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(550, 30) 30\n",
"(180, 30) 30\n"
]
}
],
"source": [
"(in_price, in_indexes), (out_price, out_indexes) = roll_in_and_out_samples(price, **split_kwargs)\n",
"\n",
"print(in_price.shape, len(in_indexes)) # in-sample\n",
"print(out_price.shape, len(out_indexes)) # out-sample"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def simulate_holding(price, **kwargs):\n",
" pf = vbt.Portfolio.from_holding(price, **kwargs)\n",
" return pf.sharpe_ratio()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"split_idx\n",
"0 0.229171\n",
"1 0.493885\n",
"2 1.576114\n",
"3 1.255349\n",
"4 1.697116\n",
"5 1.606954\n",
"6 2.000155\n",
"7 2.398767\n",
"8 2.377776\n",
"9 2.539653\n",
"10 3.037085\n",
"11 2.605911\n",
"12 2.259943\n",
"13 2.064727\n",
"14 1.840279\n",
"15 1.695307\n",
"16 0.631063\n",
"17 0.493426\n",
"18 0.466068\n",
"19 -0.139716\n",
"20 0.576409\n",
"21 0.395839\n",
"22 0.402171\n",
"23 0.779667\n",
"24 0.484766\n",
"25 1.206379\n",
"26 1.356616\n",
"27 1.193132\n",
"28 1.207407\n",
"29 1.740877\n",
"Name: sharpe_ratio, dtype: float64\n"
]
}
],
"source": [
"in_hold_sharpe = simulate_holding(in_price, **pf_kwargs)\n",
"\n",
"print(in_hold_sharpe)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"def simulate_all_params(price, windows, **kwargs):\n",
" fast_ma, slow_ma = vbt.MA.run_combs(price, windows, r=2, short_names=['fast', 'slow'])\n",
" entries = fast_ma.ma_crossed_above(slow_ma)\n",
" exits = fast_ma.ma_crossed_below(slow_ma)\n",
" pf = vbt.Portfolio.from_signals(price, entries, exits, **kwargs)\n",
" return pf.sharpe_ratio()"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"fast_window slow_window split_idx\n",
"10 11 0 0.856870\n",
" 1 1.125426\n",
" 2 0.497444\n",
" 3 0.366434\n",
" 4 0.845251\n",
" ... \n",
"48 49 25 -0.072416\n",
" 26 -0.403375\n",
" 27 -1.093233\n",
" 28 -0.921787\n",
" 29 -0.593033\n",
"Name: sharpe_ratio, Length: 23400, dtype: float64\n"
]
}
],
"source": [
"# Simulate all params for in-sample ranges\n",
"in_sharpe = simulate_all_params(in_price, windows, **pf_kwargs)\n",
"\n",
"print(in_sharpe)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"def get_best_index(performance, higher_better=True):\n",
" if higher_better:\n",
" return performance[performance.groupby('split_idx').idxmax()].index\n",
" return performance[performance.groupby('split_idx').idxmin()].index"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"MultiIndex([(27, 29, 0),\n",
" (19, 27, 1),\n",
" (21, 25, 2),\n",
" (23, 25, 3),\n",
" (23, 25, 4),\n",
" (44, 45, 5),\n",
" (30, 48, 6),\n",
" (37, 43, 7),\n",
" (10, 21, 8),\n",
" (10, 21, 9),\n",
" (10, 21, 10),\n",
" (10, 21, 11),\n",
" (10, 21, 12),\n",
" (10, 21, 13),\n",
" (10, 21, 14),\n",
" (10, 22, 15),\n",
" (10, 22, 16),\n",
" (10, 22, 17),\n",
" (17, 22, 18),\n",
" (18, 19, 19),\n",
" (13, 21, 20),\n",
" (45, 49, 21),\n",
" (45, 49, 22),\n",
" (18, 21, 23),\n",
" (13, 21, 24),\n",
" (15, 18, 25),\n",
" (13, 20, 26),\n",
" (13, 20, 27),\n",
" (13, 20, 28),\n",
" (13, 20, 29)],\n",
" names=['fast_window', 'slow_window', 'split_idx'])\n"
]
}
],
"source": [
"in_best_index = get_best_index(in_sharpe)\n",
"\n",
"print(in_best_index)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"def get_best_params(best_index, level_name):\n",
" return best_index.get_level_values(level_name).to_numpy()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[27 29]\n",
" [19 27]\n",
" [21 25]\n",
" [23 25]\n",
" [23 25]\n",
" [44 45]\n",
" [30 48]\n",
" [37 43]\n",
" [10 21]\n",
" [10 21]\n",
" [10 21]\n",
" [10 21]\n",
" [10 21]\n",
" [10 21]\n",
" [10 21]\n",
" [10 22]\n",
" [10 22]\n",
" [10 22]\n",
" [17 22]\n",
" [18 19]\n",
" [13 21]\n",
" [45 49]\n",
" [45 49]\n",
" [18 21]\n",
" [13 21]\n",
" [15 18]\n",
" [13 20]\n",
" [13 20]\n",
" [13 20]\n",
" [13 20]]\n"
]
}
],
"source": [
"in_best_fast_windows = get_best_params(in_best_index, 'fast_window')\n",
"in_best_slow_windows = get_best_params(in_best_index, 'slow_window')\n",
"in_best_window_pairs = np.array(list(zip(in_best_fast_windows, in_best_slow_windows)))\n",
"\n",
"print(in_best_window_pairs)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"pd.DataFrame(in_best_window_pairs, columns=['fast_window', 'slow_window']).vbt.plot().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"split_idx\n",
"0 1.773721\n",
"1 2.255167\n",
"2 1.605040\n",
"3 2.302425\n",
"4 3.470567\n",
"5 3.208988\n",
"6 3.335303\n",
"7 3.245646\n",
"8 3.099480\n",
"9 1.633831\n",
"10 -0.118486\n",
"11 0.140842\n",
"12 0.192643\n",
"13 -1.635978\n",
"14 -1.918136\n",
"15 -0.525469\n",
"16 2.720792\n",
"17 3.453221\n",
"18 1.631937\n",
"19 0.085609\n",
"20 0.291843\n",
"21 0.060253\n",
"22 1.204436\n",
"23 0.876595\n",
"24 2.097729\n",
"25 3.843597\n",
"26 4.741930\n",
"27 3.892515\n",
"28 1.066166\n",
"29 0.470709\n",
"Name: sharpe_ratio, dtype: float64\n"
]
}
],
"source": [
"out_hold_sharpe = simulate_holding(out_price, **pf_kwargs)\n",
"\n",
"print(out_hold_sharpe)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"fast_window slow_window split_idx\n",
"10 11 0 -1.018512\n",
" 1 -0.279485\n",
" 2 -0.097736\n",
" 3 1.209532\n",
" 4 2.518178\n",
" ... \n",
"48 49 25 0.938274\n",
" 26 4.069947\n",
" 27 3.016070\n",
" 28 0.108060\n",
" 29 -0.482026\n",
"Name: sharpe_ratio, Length: 23400, dtype: float64\n"
]
}
],
"source": [
"# Simulate all params for out-sample ranges\n",
"out_sharpe = simulate_all_params(out_price, windows, **pf_kwargs)\n",
"\n",
"print(out_sharpe)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"def simulate_best_params(price, best_fast_windows, best_slow_windows, **kwargs):\n",
" fast_ma = vbt.MA.run(price, window=best_fast_windows, per_column=True)\n",
" slow_ma = vbt.MA.run(price, window=best_slow_windows, per_column=True)\n",
" entries = fast_ma.ma_crossed_above(slow_ma)\n",
" exits = fast_ma.ma_crossed_below(slow_ma)\n",
" pf = vbt.Portfolio.from_signals(price, entries, exits, **kwargs)\n",
" return pf.sharpe_ratio()"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ma_window ma_window split_idx\n",
"27 29 0 1.164082\n",
"19 27 1 0.501229\n",
"21 25 2 1.414590\n",
"23 25 3 1.031654\n",
" 4 1.431372\n",
"44 45 5 1.120318\n",
"30 48 6 0.425328\n",
"37 43 7 -0.564391\n",
"10 21 8 3.439371\n",
" 9 2.783249\n",
" 10 1.187116\n",
" 11 1.771893\n",
" 12 0.341544\n",
" 13 0.065343\n",
" 14 0.030836\n",
" 22 15 1.702848\n",
" 16 2.727809\n",
" 17 0.861896\n",
"17 22 18 0.476459\n",
"18 19 19 0.433287\n",
"13 21 20 1.762454\n",
"45 49 21 -2.053022\n",
" 22 -1.580201\n",
"18 21 23 0.849560\n",
"13 21 24 1.261873\n",
"15 18 25 4.088163\n",
"13 20 26 3.851092\n",
" 27 1.234676\n",
" 28 -1.773549\n",
" 29 -1.803552\n",
"Name: sharpe_ratio, dtype: float64\n"
]
}
],
"source": [
"# Use best params from in-sample ranges and simulate them for out-sample ranges\n",
"out_test_sharpe = simulate_best_params(out_price, in_best_fast_windows, in_best_slow_windows, **pf_kwargs)\n",
"\n",
"print(out_test_sharpe)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"cv_results_df = pd.DataFrame({\n",
" 'in_sample_hold': in_hold_sharpe.values,\n",
" 'in_sample_median': in_sharpe.groupby('split_idx').median().values,\n",
" 'in_sample_best': in_sharpe[in_best_index].values,\n",
" 'out_sample_hold': out_hold_sharpe.values,\n",
" 'out_sample_median': out_sharpe.groupby('split_idx').median().values,\n",
" 'out_sample_test': out_test_sharpe.values\n",
"})"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"color_schema = vbt.settings['plotting']['color_schema']\n",
"\n",
"cv_results_df.vbt.plot(\n",
" trace_kwargs=[\n",
" dict(line_color=color_schema['blue']),\n",
" dict(line_color=color_schema['blue'], line_dash='dash'),\n",
" dict(line_color=color_schema['blue'], line_dash='dot'),\n",
" dict(line_color=color_schema['orange']),\n",
" dict(line_color=color_schema['orange'], line_dash='dash'),\n",
" dict(line_color=color_schema['orange'], line_dash='dot')\n",
" ]\n",
").show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}