{ "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": [ "2015201620172018201920202021010k20k30k40k50k60kClose" ] }, "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": [ "201520162017201820192020202129262320171411852in-sampleout-sample" ] }, "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": [ "05101520251020304050fast_windowslow_window" ] }, "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": [ "0510152025−2024in_sample_holdin_sample_medianin_sample_bestout_sample_holdout_sample_medianout_sample_test" ] }, "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 }