|
3322 | 3322 | "source": [
|
3323 | 3323 | "[Link to tsmoothie](https://bit.ly/3L8KXky)."
|
3324 | 3324 | ]
|
| 3325 | + }, |
| 3326 | + { |
| 3327 | + "cell_type": "markdown", |
| 3328 | + "id": "f2e850ef", |
| 3329 | + "metadata": {}, |
| 3330 | + "source": [ |
| 3331 | + "### Backtesting: Assess Trading Strategy Performance Effortlessly in Python" |
| 3332 | + ] |
| 3333 | + }, |
| 3334 | + { |
| 3335 | + "cell_type": "code", |
| 3336 | + "execution_count": null, |
| 3337 | + "id": "0ff27227", |
| 3338 | + "metadata": { |
| 3339 | + "tags": [ |
| 3340 | + "hide-cell" |
| 3341 | + ] |
| 3342 | + }, |
| 3343 | + "outputs": [], |
| 3344 | + "source": [ |
| 3345 | + "!pip install -U backtesting" |
| 3346 | + ] |
| 3347 | + }, |
| 3348 | + { |
| 3349 | + "cell_type": "markdown", |
| 3350 | + "id": "0288e5d6", |
| 3351 | + "metadata": {}, |
| 3352 | + "source": [ |
| 3353 | + "Evaluating trading strategies' effectiveness is crucial for financial decision-making, but it's challenging due to the complexities of historical data analysis and strategy testing. \n", |
| 3354 | + "\n", |
| 3355 | + "Backtesting allows users to simulate trades based on historical data and visualize the outcomes through interactive plots in three lines of code.\n", |
| 3356 | + "\n", |
| 3357 | + "To see how Backtesting works, let's create our first strategy to backtest on these Google data, a simple moving average (MA) cross-over strategy." |
| 3358 | + ] |
| 3359 | + }, |
| 3360 | + { |
| 3361 | + "cell_type": "code", |
| 3362 | + "execution_count": 1, |
| 3363 | + "id": "4f410242", |
| 3364 | + "metadata": {}, |
| 3365 | + "outputs": [ |
| 3366 | + { |
| 3367 | + "data": { |
| 3368 | + "text/html": [ |
| 3369 | + "<div>\n", |
| 3370 | + "<style scoped>\n", |
| 3371 | + " .dataframe tbody tr th:only-of-type {\n", |
| 3372 | + " vertical-align: middle;\n", |
| 3373 | + " }\n", |
| 3374 | + "\n", |
| 3375 | + " .dataframe tbody tr th {\n", |
| 3376 | + " vertical-align: top;\n", |
| 3377 | + " }\n", |
| 3378 | + "\n", |
| 3379 | + " .dataframe thead th {\n", |
| 3380 | + " text-align: right;\n", |
| 3381 | + " }\n", |
| 3382 | + "</style>\n", |
| 3383 | + "<table border=\"1\" class=\"dataframe\">\n", |
| 3384 | + " <thead>\n", |
| 3385 | + " <tr style=\"text-align: right;\">\n", |
| 3386 | + " <th></th>\n", |
| 3387 | + " <th>Open</th>\n", |
| 3388 | + " <th>High</th>\n", |
| 3389 | + " <th>Low</th>\n", |
| 3390 | + " <th>Close</th>\n", |
| 3391 | + " <th>Volume</th>\n", |
| 3392 | + " </tr>\n", |
| 3393 | + " </thead>\n", |
| 3394 | + " <tbody>\n", |
| 3395 | + " <tr>\n", |
| 3396 | + " <th>2013-02-25</th>\n", |
| 3397 | + " <td>802.3</td>\n", |
| 3398 | + " <td>808.41</td>\n", |
| 3399 | + " <td>790.49</td>\n", |
| 3400 | + " <td>790.77</td>\n", |
| 3401 | + " <td>2303900</td>\n", |
| 3402 | + " </tr>\n", |
| 3403 | + " <tr>\n", |
| 3404 | + " <th>2013-02-26</th>\n", |
| 3405 | + " <td>795.0</td>\n", |
| 3406 | + " <td>795.95</td>\n", |
| 3407 | + " <td>784.40</td>\n", |
| 3408 | + " <td>790.13</td>\n", |
| 3409 | + " <td>2202500</td>\n", |
| 3410 | + " </tr>\n", |
| 3411 | + " <tr>\n", |
| 3412 | + " <th>2013-02-27</th>\n", |
| 3413 | + " <td>794.8</td>\n", |
| 3414 | + " <td>804.75</td>\n", |
| 3415 | + " <td>791.11</td>\n", |
| 3416 | + " <td>799.78</td>\n", |
| 3417 | + " <td>2026100</td>\n", |
| 3418 | + " </tr>\n", |
| 3419 | + " <tr>\n", |
| 3420 | + " <th>2013-02-28</th>\n", |
| 3421 | + " <td>801.1</td>\n", |
| 3422 | + " <td>806.99</td>\n", |
| 3423 | + " <td>801.03</td>\n", |
| 3424 | + " <td>801.20</td>\n", |
| 3425 | + " <td>2265800</td>\n", |
| 3426 | + " </tr>\n", |
| 3427 | + " <tr>\n", |
| 3428 | + " <th>2013-03-01</th>\n", |
| 3429 | + " <td>797.8</td>\n", |
| 3430 | + " <td>807.14</td>\n", |
| 3431 | + " <td>796.15</td>\n", |
| 3432 | + " <td>806.19</td>\n", |
| 3433 | + " <td>2175400</td>\n", |
| 3434 | + " </tr>\n", |
| 3435 | + " </tbody>\n", |
| 3436 | + "</table>\n", |
| 3437 | + "</div>" |
| 3438 | + ], |
| 3439 | + "text/plain": [ |
| 3440 | + " Open High Low Close Volume\n", |
| 3441 | + "2013-02-25 802.3 808.41 790.49 790.77 2303900\n", |
| 3442 | + "2013-02-26 795.0 795.95 784.40 790.13 2202500\n", |
| 3443 | + "2013-02-27 794.8 804.75 791.11 799.78 2026100\n", |
| 3444 | + "2013-02-28 801.1 806.99 801.03 801.20 2265800\n", |
| 3445 | + "2013-03-01 797.8 807.14 796.15 806.19 2175400" |
| 3446 | + ] |
| 3447 | + }, |
| 3448 | + "execution_count": 1, |
| 3449 | + "metadata": {}, |
| 3450 | + "output_type": "execute_result" |
| 3451 | + } |
| 3452 | + ], |
| 3453 | + "source": [ |
| 3454 | + "from backtesting.test import GOOG\n", |
| 3455 | + "\n", |
| 3456 | + "GOOG.tail()" |
| 3457 | + ] |
| 3458 | + }, |
| 3459 | + { |
| 3460 | + "cell_type": "code", |
| 3461 | + "execution_count": 2, |
| 3462 | + "id": "b3230aaf", |
| 3463 | + "metadata": {}, |
| 3464 | + "outputs": [], |
| 3465 | + "source": [ |
| 3466 | + "import pandas as pd\n", |
| 3467 | + "\n", |
| 3468 | + "\n", |
| 3469 | + "def SMA(values, n):\n", |
| 3470 | + " \"\"\"\n", |
| 3471 | + " Return simple moving average of `values`, at\n", |
| 3472 | + " each step taking into account `n` previous values.\n", |
| 3473 | + " \"\"\"\n", |
| 3474 | + " return pd.Series(values).rolling(n).mean()" |
| 3475 | + ] |
| 3476 | + }, |
| 3477 | + { |
| 3478 | + "cell_type": "code", |
| 3479 | + "execution_count": 3, |
| 3480 | + "id": "cbb93184", |
| 3481 | + "metadata": {}, |
| 3482 | + "outputs": [], |
| 3483 | + "source": [ |
| 3484 | + "from backtesting import Strategy\n", |
| 3485 | + "from backtesting.lib import crossover\n", |
| 3486 | + "\n", |
| 3487 | + "\n", |
| 3488 | + "class SmaCross(Strategy):\n", |
| 3489 | + " # Define the two MA lags as *class variables*\n", |
| 3490 | + " # for later optimization\n", |
| 3491 | + " n1 = 10\n", |
| 3492 | + " n2 = 20\n", |
| 3493 | + "\n", |
| 3494 | + " def init(self):\n", |
| 3495 | + " # Precompute the two moving averages\n", |
| 3496 | + " self.sma1 = self.I(SMA, self.data.Close, self.n1)\n", |
| 3497 | + " self.sma2 = self.I(SMA, self.data.Close, self.n2)\n", |
| 3498 | + "\n", |
| 3499 | + " def next(self):\n", |
| 3500 | + " # If sma1 crosses above sma2, close any existing\n", |
| 3501 | + " # short trades, and buy the asset\n", |
| 3502 | + " if crossover(self.sma1, self.sma2):\n", |
| 3503 | + " self.position.close()\n", |
| 3504 | + " self.buy()\n", |
| 3505 | + "\n", |
| 3506 | + " # Else, if sma1 crosses below sma2, close any existing\n", |
| 3507 | + " # long trades, and sell the asset\n", |
| 3508 | + " elif crossover(self.sma2, self.sma1):\n", |
| 3509 | + " self.position.close()\n", |
| 3510 | + " self.sell()" |
| 3511 | + ] |
| 3512 | + }, |
| 3513 | + { |
| 3514 | + "cell_type": "markdown", |
| 3515 | + "id": "94bf31a7", |
| 3516 | + "metadata": {}, |
| 3517 | + "source": [ |
| 3518 | + "To assess the performance of our investment strategy, we will instantiate a `Backtest` object, using Google stock data as our asset of interest and incorporating the `SmaCross` strategy class. We'll start with an initial cash balance of 10,000 units and set the broker's commission to a realistic rate of 0.2%." |
| 3519 | + ] |
| 3520 | + }, |
| 3521 | + { |
| 3522 | + "cell_type": "code", |
| 3523 | + "execution_count": 4, |
| 3524 | + "id": "4c73c152", |
| 3525 | + "metadata": {}, |
| 3526 | + "outputs": [ |
| 3527 | + { |
| 3528 | + "data": { |
| 3529 | + "text/plain": [ |
| 3530 | + "Start 2004-08-19 00:00:00\n", |
| 3531 | + "End 2013-03-01 00:00:00\n", |
| 3532 | + "Duration 3116 days 00:00:00\n", |
| 3533 | + "Exposure Time [%] 97.067039\n", |
| 3534 | + "Equity Final [$] 68221.96986\n", |
| 3535 | + "Equity Peak [$] 68991.21986\n", |
| 3536 | + "Return [%] 582.219699\n", |
| 3537 | + "Buy & Hold Return [%] 703.458242\n", |
| 3538 | + "Return (Ann.) [%] 25.266427\n", |
| 3539 | + "Volatility (Ann.) [%] 38.383008\n", |
| 3540 | + "Sharpe Ratio 0.658271\n", |
| 3541 | + "Sortino Ratio 1.288779\n", |
| 3542 | + "Calmar Ratio 0.763748\n", |
| 3543 | + "Max. Drawdown [%] -33.082172\n", |
| 3544 | + "Avg. Drawdown [%] -5.581506\n", |
| 3545 | + "Max. Drawdown Duration 688 days 00:00:00\n", |
| 3546 | + "Avg. Drawdown Duration 41 days 00:00:00\n", |
| 3547 | + "# Trades 94\n", |
| 3548 | + "Win Rate [%] 54.255319\n", |
| 3549 | + "Best Trade [%] 57.11931\n", |
| 3550 | + "Worst Trade [%] -16.629898\n", |
| 3551 | + "Avg. Trade [%] 2.074326\n", |
| 3552 | + "Max. Trade Duration 121 days 00:00:00\n", |
| 3553 | + "Avg. Trade Duration 33 days 00:00:00\n", |
| 3554 | + "Profit Factor 2.190805\n", |
| 3555 | + "Expectancy [%] 2.606294\n", |
| 3556 | + "SQN 1.990216\n", |
| 3557 | + "_strategy SmaCross\n", |
| 3558 | + "_equity_curve ...\n", |
| 3559 | + "_trades Size EntryB...\n", |
| 3560 | + "dtype: object" |
| 3561 | + ] |
| 3562 | + }, |
| 3563 | + "execution_count": 4, |
| 3564 | + "metadata": {}, |
| 3565 | + "output_type": "execute_result" |
| 3566 | + } |
| 3567 | + ], |
| 3568 | + "source": [ |
| 3569 | + "from backtesting import Backtest\n", |
| 3570 | + "\n", |
| 3571 | + "bt = Backtest(GOOG, SmaCross, cash=10_000, commission=.002)\n", |
| 3572 | + "stats = bt.run()\n", |
| 3573 | + "stats" |
| 3574 | + ] |
| 3575 | + }, |
| 3576 | + { |
| 3577 | + "cell_type": "markdown", |
| 3578 | + "id": "f3c91c05", |
| 3579 | + "metadata": {}, |
| 3580 | + "source": [ |
| 3581 | + "Plot the outcomes:" |
| 3582 | + ] |
| 3583 | + }, |
| 3584 | + { |
| 3585 | + "cell_type": "code", |
| 3586 | + "execution_count": null, |
| 3587 | + "id": "7a5f7bf7", |
| 3588 | + "metadata": {}, |
| 3589 | + "outputs": [], |
| 3590 | + "source": [ |
| 3591 | + "bt.plot()" |
| 3592 | + ] |
| 3593 | + }, |
| 3594 | + { |
| 3595 | + "cell_type": "markdown", |
| 3596 | + "id": "00f1fa47", |
| 3597 | + "metadata": {}, |
| 3598 | + "source": [ |
| 3599 | + "" |
| 3600 | + ] |
| 3601 | + }, |
| 3602 | + { |
| 3603 | + "cell_type": "markdown", |
| 3604 | + "id": "f9e0ead1", |
| 3605 | + "metadata": {}, |
| 3606 | + "source": [ |
| 3607 | + "[Link to Backtesting](https://bit.ly/3zwi0g3)." |
| 3608 | + ] |
3325 | 3609 | }
|
3326 | 3610 | ],
|
3327 | 3611 | "metadata": {
|
|
0 commit comments