{ "cells": [ { "cell_type": "markdown", "id": "c9dc167c", "metadata": {}, "source": [ "# How to create a trading bot in Python (4) with backtesting (vectorBT), MA, MACD, RSI, Stochastic, BB, MFI, 8 Candlestick Patterns, and live paper-trading (Alpaca) - PART 1" ] }, { "cell_type": "markdown", "id": "e23d03e2", "metadata": {}, "source": [ "# What you'll see in this tutorial:\n", "## 1. How to easily create and implement a trading strategy in Python\n", "## 2. How to optimize strategy during backtesting for crypto (e.g. BTCUSD)\n", "## 3. Live paper-trading crypto (e.g. BTCUSD) with strategy optmized during backtesting\n", "## 4. How to optimize strategy during backtesting for stocks (e.g. AAPL)\n", "## 5. Live paper-trading with stocks (e.g. AAPL) with strategy optmized during backtesting\n", "## 6. Tips for improving the bot" ] }, { "cell_type": "markdown", "id": "12b40fe8", "metadata": {}, "source": [ "# Disclaimer\n", "\n", "### Before we start coding, you should know that this bot is created for educational purposes. It uses 'paper account' to trade (does not use real money) and this tutorial is not a financial advice for you, just a short programming tutorial. \n", "\n", "And that being said, let's start." ] }, { "cell_type": "markdown", "id": "00712958", "metadata": {}, "source": [ "# YOUR KEYS TO THE BOT (the only cell you have to change to make the bot work)" ] }, { "cell_type": "markdown", "id": "de798b7e", "metadata": {}, "source": [ "These 2 lines of code are the only two lines you have to change to make the bot work. You need to put here your own keys to Alpaca account. At the moment of creating this tutorial, Alpaca is free to use for everyone and with paper account you only need an email address to sign up." ] }, { "cell_type": "code", "execution_count": null, "id": "63bc2e6d", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "KEY_ID = \"your own KEY_ID\" #replace it with your own KEY_ID from Alpaca: https://alpaca.markets/\n", "SECRET_KEY = \"your own SECRET_KEY\" #replace it with your own SECRET_KEY from Alpaca" ] }, { "cell_type": "markdown", "id": "a0043fe4", "metadata": {}, "source": [ "# ASSET TO TRADE" ] }, { "cell_type": "markdown", "id": "926564d1", "metadata": {}, "source": [ "You can choose any asset that can be traded with Alpaca API and paste the ticker of the asset in here. " ] }, { "cell_type": "code", "execution_count": null, "id": "7fab380f", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "\n", "(asset, asset_type, rounding) = (\"BTCUSD\", \"crypto\", 0)\n", "\n", "#if you want to trade crypto check: https://alpaca.markets/support/what-cryptocurrencies-does-alpaca-currently-support/\n", "#rounding declares the no of numbers after comma for orders\n", "#According to Alpaca: \n", "#\"All cryptocurrency assets are fractionable but the supported decimal points vary depending on the cryptocurrency.\"\n", "#Check the smallest qty of the crypto you want to trade before declaring the 'rounding'\n", "#read more about minimum qty and qty increment at https://alpaca.markets/docs/trading/crypto-trading/\n", "\n", "#SuperAI Trading Bot\n", "\n", "#(asset, asset_type, data_source) = (\"AAPL\", \"stock\", \"Yahoo\")\n", "\n", "#if you want to trade stocks replace it with the ticker of the company you prefer: https://www.nyse.com/listings_directory/stock\n", "#you can also use \"Alpaca\" as a data_source\n", "#Alpaca gives you free access to more historical data, but in a free plan doesn't allow you to access data from last 15 minutes\n", "#Yahoo gives you access to data from last 15 minutes, but gives you only 7 days of historical data with 1-min interval at a time" ] }, { "cell_type": "markdown", "id": "0f5a82df", "metadata": {}, "source": [ "# THE BRAIN OF THE BOT" ] }, { "cell_type": "markdown", "id": "0a3cfaac", "metadata": {}, "source": [ "Here you can declare which technical indicators and candlestick patterns you want to use as signals to buy and sell stocks. \n", "You have: \n", "1. TREND INDICATORS: EMA (Exponential Moving Average), MACD (Moving Average Convergence Divergence)\n", "2. MOMENTUM INDICATORS: RSI (Relative Strength Index), Stoch (Stochastic) \n", "3. VOLATILITY: BB (Bollinger Bands)\n", "4. VOLUME: MFI (Money Flow Index)\n", "5. BUY CANDLE PATTERNS: from 1 candle: Hammer, from 3 candles: Morning Star, 3 White Soldiers\n", "6. SELL CANDLE PATTERNS: from 1 candle: Shooting Star, from 3 candles: Evening Star, 3 Black Crows\n", "7. BUY - SELL CANDLE PATTERNS: from 2 candles: Engulfing Bullish / Bearish, from 3 candles: 3 Outside Up / Down\n", "\n", "You can create:\n", "- very simple strategy (e.g. buy when RSI < 30) or \n", "- much more complex (e.g. buy when RSI < 30 only if (MACD < 0 and FAST MA < SLOW MA))\n", "- or even much more complex one with thresholds, crossing-overs, following trends, etc.\n", "\n", "The initial logic of the bot is to:\n", "#### BUY IF:\n", "- IT'S BETWEEN DECLARED BUYING HOURS (E.G. IF BUYING STOCKS: 09:45-15:55 IN NYC)\n", "AND:\n", "\n", "(\n", "- (FAST MA < SLOW MA AND RSI < RSI_OVERSOLD_THRESHOLD)\n", "- OR LAST CLOSE PRICE < BB_LOW\n", "- OR MFI < MFI_OVERSOLD_THRESHOLD\n", "- OR AT LEAST 1 CANDLESTICK PATTERN SIGNALS TO BUY\n", "\n", ")\n", "- AND YOU DON'T GET SIGNAL TO SELL FROM THE FOLLOWING SELLING LOGIC:\n", "\n", "#### SELL IF:\n", "- IT'S BETWEEN DECLARED FORCED SELLING HOURS (E.G. IF SELLING STOCKS: AFTER 15:55 IN NYC) \n", "- OR RSI > RSI_OVERBOUGHT_THRESHOLD\n", "- OR LAST CLOSE PRICE > BB_HIGH\n", "- OR MFI > MFI_OVERBOUGHT_THRESHOLD\n", "- OR AT LEAST 1 CANDLESTICK PATTERN SIGNALS TO SELL" ] }, { "cell_type": "markdown", "id": "4b89b487", "metadata": {}, "source": [ "## Function for creating a SuperAI trading signal (buy / sell / do nothing)" ] }, { "cell_type": "code", "execution_count": null, "id": "9500b58f", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#@njit #you can unhash it if you have numba installed and imported and the bot should work a little faster, if you use njit\n", "def create_signal(open_prices, high_prices, low_prices, close_prices, volume,\n", " buylesstime, selltime, #Time to abstain from buying and forced selling\n", " ma, ma_fast, ma_slow, #Moving Average\n", " macd, macd_diff, macd_sign, #Moving Average Convergence Divergence\n", " rsi, rsi_entry, rsi_exit, #Relative Strength Index\n", " stoch, stoch_signal, stoch_entry, stoch_exit, #Stochastic\n", " bb_low, bb_high, #Bollinger Bands\n", " mfi, mfi_entry, mfi_exit, #Money Flow Index\n", " candle_buy_signal_1, candle_buy_signal_2, candle_buy_signal_3, #Candle signals to buy\n", " candle_sell_signal_1, candle_sell_signal_2, candle_sell_signal_3, #Candle signals to sell\n", " candle_buy_sell_signal_1, candle_buy_sell_signal_2): #Candle signals to buy or sell\n", " \n", " \n", " SuperAI_signal_buy = np.where( \n", " (buylesstime != 1) \n", " &\n", " (\n", " (\n", " ((ma_fast < ma_slow) & (rsi < rsi_entry))\n", " | \n", " (close_prices < bb_low)\n", " | \n", " (mfi < mfi_entry)\n", " #| \n", " #(close_prices > ma)\n", " #| \n", " #(macd < 0)\n", " #| \n", " #(macd_diff < 0)\n", " #| \n", " #(macd_sign < macd)\n", " #| \n", " #(stoch < stoch_entry)\n", " #| \n", " #(stoch_signal < stoch_entry)\n", " #| \n", " #((close_prices > ma) & (ma_fast > ma_slow))\n", " #| \n", " #(((rsi < rsi_entry) & ((close_prices < ma) | (macd < 0))))\n", " #| \n", " #((close_prices > ma) & (shifted(close_prices, 1) < ma))\n", " #| \n", " #((stoch < stoch_entry) & (\n", " # (ma > (shifted(ma, 1))) & \n", " # (shifted(ma, 1) > (shifted(ma, 2)))))\n", " #| \n", " #((rsi < rsi_entry) & (\n", " # ~((ma > (shifted(ma, 2) * 1.01)) & \n", " # (shifted(ma, 2) > (shifted(ma, 4)*1.01)))))\n", " )\n", " | \n", " ((candle_buy_signal_1 > 0) | (candle_buy_signal_2 > 0) | (candle_buy_signal_3 > 0)\n", " | (candle_buy_sell_signal_1 > 0) | (candle_buy_sell_signal_2 > 0))\n", " )\n", " , 1, 0) #1 is buy, -1 is sell, 0 is do nothing\n", " SuperAI_signal = np.where( \n", " (selltime == 1)\n", " #| \n", " #(ma_fast > ma_slow) \n", " | \n", " (rsi > rsi_exit)\n", " | \n", " (close_prices > bb_high)\n", " | \n", " (mfi > mfi_exit)\n", " #| \n", " #(close_prices < ma)\n", " #| \n", " #(macd_diff > 0)\n", " #| \n", " #(macd_sign > macd)\n", " #| \n", " #(stoch > stoch_exit)\n", " #| \n", " #(stoch_signal > stoch_exit)\n", " #| \n", " #((close_prices < ma) & (rsi > rsi_exit))\n", " #| \n", " #(((rsi > rsi_exit) & ((close_prices < ma) | (macd > 3))))\n", " #| \n", " #((close_prices < ma) & (shifted(close_prices, 1) > ma))\n", " #| \n", " #((stoch > stoch_exit) & (\n", " # (ma > (shifted(ma, 1))) & \n", " # (shifted(ma, 1) > (shifted(ma, 2)))))\n", " | \n", " ((candle_sell_signal_1 < 0) | (candle_sell_signal_2 < 0) | (candle_buy_signal_3 < 0)\n", " | (candle_buy_sell_signal_1 < 0) | (candle_buy_sell_signal_2 < 0))\n", " , -1, SuperAI_signal_buy) #1 is buy, -1 is sell, 0 is do nothing\n", " return SuperAI_signal" ] }, { "cell_type": "markdown", "id": "c4f3714a", "metadata": {}, "source": [ "## Parameters for taking profit and stopping loss (no matter the trading signal)" ] }, { "cell_type": "code", "execution_count": null, "id": "e8f9547d", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "take_profit_percent = 0.05 # 5%\n", "stop_loss_percent = 0.001 # 0.1%" ] }, { "cell_type": "markdown", "id": "8c1a372f", "metadata": {}, "source": [ "## Helper function for declaring the time of trading (buying / selling / doing nothing)" ] }, { "cell_type": "code", "execution_count": null, "id": "57d6f45f", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def trading_buy_sell_time():\n", " if asset_type == 'stock':\n", " #more about trading hours at: https://alpaca.markets/docs/trading/orders/#extended-hours-trading\n", " trading_hour_start = \"09:30\"\n", " trading_hour_stop = \"16:00\" \n", " #time when you don't want to buy at the beginning of the day \n", " buyless_time_start_1 = \"09:30\"\n", " buyless_time_end_1 = \"09:45\"\n", " buyless_time_start_2 = \"15:55\"\n", " buyless_time_end_2 = \"16:00\"\n", " #time when you want to sell by the end of the day\n", " selltime_start = \"15:55\"\n", " selltime_end = \"16:00\"\n", "\n", " elif asset_type == 'crypto':\n", " trading_hour_start = \"00:00\"\n", " trading_hour_stop = \"23:59\" \n", " #time when you don't want to buy at the beginning of the day \n", " buyless_time_start_1 = \"23:59\"\n", " buyless_time_end_1 = \"00:01\"\n", " buyless_time_start_2 = \"23:58\"\n", " buyless_time_end_2 = \"23:59\"\n", " #time when you want to sell by the end of the day\n", " selltime_start = \"23:59\"\n", " selltime_end = \"00:00\" \n", " return (trading_hour_start, trading_hour_stop, \n", " buyless_time_start_1, buyless_time_end_1, buyless_time_start_2, buyless_time_end_2,\n", " selltime_start, selltime_end)" ] }, { "cell_type": "code", "execution_count": null, "id": "127d398a", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "(trading_hour_start, trading_hour_stop, \n", " buyless_time_start_1, buyless_time_end_1, buyless_time_start_2, buyless_time_end_2,\n", " selltime_start, selltime_end) = trading_buy_sell_time()" ] }, { "cell_type": "code", "execution_count": null, "id": "107d420c", "metadata": {}, "outputs": [], "source": [ "(trading_hour_start, trading_hour_stop, \n", " buyless_time_start_1, buyless_time_end_1, buyless_time_start_2, buyless_time_end_2,\n", " selltime_start, selltime_end)" ] }, { "cell_type": "markdown", "id": "4c172795", "metadata": {}, "source": [ "## Helper function for calculating 'shifted' data" ] }, { "cell_type": "code", "execution_count": null, "id": "2357c4e7", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#helper function to shift data in order to test differences between data from x min and data from x-time min\n", "\n", "def shifted(data, shift_window):\n", " data_shifted = np.roll(data, shift_window)\n", " if shift_window >= 0:\n", " data_shifted[:shift_window]=np.NaN\n", " elif shift_window < 0:\n", " data_shifted[shift_window:]=np.NaN\n", " return data_shifted" ] }, { "cell_type": "markdown", "id": "8551b01a", "metadata": {}, "source": [ "## Basic parameters for technical indicators used by SuperAI Trading Bot" ] }, { "cell_type": "code", "execution_count": null, "id": "9fa33b44", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "# Moving Average\n", "ma_timeframe = 28\n", "ma_fast_timeframe = 14\n", "ma_slow_timeframe = 50" ] }, { "cell_type": "code", "execution_count": null, "id": "4bca044a", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "# Moving Average Convergence Divergence\n", "macd_slow_timeframe = 26\n", "macd_fast_timeframe = 12\n", "macd_signal_timeframe = 9" ] }, { "cell_type": "code", "execution_count": null, "id": "e177051a", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "# Relative Strength Index\n", "rsi_timeframe = 14\n", "rsi_oversold_threshold = 30\n", "rsi_overbought_threshold = 70" ] }, { "cell_type": "code", "execution_count": null, "id": "ed42a4bd", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#Stochastic Indicator\n", "stoch_timeframe = 14\n", "stoch_smooth_timeframe = 3 \n", "stoch_oversold_threshold = 20 \n", "stoch_overbought_threshold = 80" ] }, { "cell_type": "code", "execution_count": null, "id": "454bcacc", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "# Bollinger Bands\n", "bb_timeframe = 10\n", "bb_dev = 2" ] }, { "cell_type": "code", "execution_count": null, "id": "b91d2829", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "# Money Flow Index\n", "mfi_timeframe = 14\n", "mfi_oversold_threshold = 20\n", "mfi_overbought_threshold = 80" ] }, { "cell_type": "code", "execution_count": null, "id": "166d5593", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "ma_window = ma_timeframe\n", "ma_fast_window = ma_fast_timeframe\n", "ma_slow_window = ma_slow_timeframe\n", "macd_slow_window = macd_slow_timeframe\n", "macd_fast_window = macd_fast_timeframe\n", "macd_sign_window = macd_signal_timeframe\n", "rsi_window = rsi_timeframe\n", "rsi_entry = rsi_oversold_threshold\n", "rsi_exit = rsi_overbought_threshold \n", "stoch_window = stoch_timeframe\n", "stoch_smooth_window = stoch_smooth_timeframe\n", "stoch_entry = stoch_oversold_threshold\n", "stoch_exit = stoch_overbought_threshold\n", "bb_window = bb_timeframe\n", "bb_dev = bb_dev\n", "mfi_window = mfi_timeframe\n", "mfi_entry = mfi_oversold_threshold\n", "mfi_exit = mfi_overbought_threshold" ] }, { "cell_type": "code", "execution_count": null, "id": "fa0d0565", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "(ma_window_max, ma_fast_window_max, ma_slow_window_max, \n", " macd_slow_window_max, macd_fast_window_max, macd_sign_window_max,\n", " rsi_window_max, rsi_entry_max, rsi_exit_max, \n", " stoch_window_max, stoch_smooth_window_max, stoch_entry_max, stoch_exit_max,\n", " bb_window_max, bb_dev_max, mfi_window_max, \n", " mfi_entry_max, mfi_exit_max) = (ma_window, ma_fast_window, ma_slow_window, \n", " macd_slow_window, macd_fast_window, macd_sign_window,\n", " rsi_window, rsi_entry, rsi_exit, \n", " stoch_window, stoch_smooth_window, stoch_entry, stoch_exit,\n", " bb_window, bb_dev, mfi_window, \n", " mfi_entry, mfi_exit)" ] }, { "cell_type": "code", "execution_count": null, "id": "3155445b", "metadata": {}, "outputs": [], "source": [ "(ma_window_max, ma_fast_window_max, ma_slow_window_max, \n", " macd_slow_window_max, macd_fast_window_max, macd_sign_window_max,\n", " rsi_window_max, rsi_entry_max, rsi_exit_max, \n", " stoch_window_max, stoch_smooth_window_max, stoch_entry_max, stoch_exit_max,\n", " bb_window_max, bb_dev_max, mfi_window_max, \n", " mfi_entry_max, mfi_exit_max)" ] }, { "cell_type": "markdown", "id": "649e15fb", "metadata": {}, "source": [ "# EXTERNAL TOOLS (LIBRARIES, PACKAGES, AND MODULES)" ] }, { "cell_type": "markdown", "id": "0a6cd616", "metadata": {}, "source": [ "## Install and update all the neccessary libraries, packages, modules" ] }, { "cell_type": "markdown", "id": "87568768", "metadata": {}, "source": [ "At first, we install all the necessary libraries and packages. You can skip it if you already have them installed or you can upgrade them." ] }, { "cell_type": "code", "execution_count": null, "id": "572c802b", "metadata": { "scrolled": true }, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "!pip install alpaca-trade-api --upgrade\n", "#https://pypi.org/project/alpaca-trade-api/" ] }, { "cell_type": "code", "execution_count": null, "id": "2fdbc9bb", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "!pip install numpy --upgrade\n", "#https://pypi.org/project/numpy/" ] }, { "cell_type": "code", "execution_count": null, "id": "b892dfce", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "!pip install pandas --upgrade\n", "#https://pypi.org/project/pandas/" ] }, { "cell_type": "code", "execution_count": null, "id": "3ad0db22", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "!pip install ta --upgrade\n", "#https://pypi.org/project/ta/" ] }, { "cell_type": "code", "execution_count": null, "id": "6acffeeb", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#!pip install TA-Lib --upgrade\n", "#https://pypi.org/project/TA-Lib/\n", "\n", "#or if it doesn't work on Windows and you are using Anaconda, \n", "#try writing in Anaconda Prompt: conda install -c conda-forge ta-lib" ] }, { "cell_type": "code", "execution_count": null, "id": "9e5969c6", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "!pip install vectorbt --upgrade\n", "#https://pypi.org/project/vectorbt/" ] }, { "cell_type": "code", "execution_count": null, "id": "e9b41bd9", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "!pip install yfinance --upgrade\n", "#https://pypi.org/project/yfinance/" ] }, { "cell_type": "markdown", "id": "d4acd736", "metadata": {}, "source": [ "## Import all the neccessary libraries, packages, modules" ] }, { "cell_type": "markdown", "id": "2d6089be", "metadata": {}, "source": [ "Now, that we have everything installed we can import it to our program." ] }, { "cell_type": "code", "execution_count": null, "id": "e0028d43", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "import alpaca_trade_api as tradeapi\n", "from alpaca_trade_api.rest import TimeFrame\n", "import ast\n", "import datetime\n", "from datetime import timedelta\n", "import json\n", "import math\n", "import numpy as np\n", "import pandas as pd\n", "import plotly\n", "import plotly.express as px\n", "import plotly.graph_objects as go\n", "import sys\n", "import ta\n", "import talib as ta_lib\n", "import time\n", "import vectorbt as vbt" ] }, { "cell_type": "markdown", "id": "1477cf28", "metadata": {}, "source": [ "### Check the versions of the tools you use" ] }, { "cell_type": "code", "execution_count": null, "id": "dc6722fa", "metadata": {}, "outputs": [], "source": [ "print(\"Python version: {}\".format(sys.version))\n", "print(\"alpaca trade api version: {}\".format(tradeapi.__version__))\n", "print(\"json version: {}\".format(json.__version__))\n", "print(\"numpy version: {}\".format(np.__version__))\n", "print(\"pandas version: {}\".format(pd.__version__))\n", "print(\"plotly version: {}\".format(plotly.__version__))\n", "print(\"ta_lib version: {}\".format(ta_lib.__version__))\n", "print(\"vectorbt version: {}\".format(vbt.__version__))" ] }, { "cell_type": "code", "execution_count": null, "id": "81f413a8", "metadata": {}, "outputs": [], "source": [ "pip show ta" ] }, { "cell_type": "markdown", "id": "14286c3d", "metadata": {}, "source": [ "## Extra tool: WARNINGS - for ignoring warnings from Jupyter Notebook (not necessary for the bot to work properly)" ] }, { "cell_type": "code", "execution_count": null, "id": "c90615dc", "metadata": {}, "outputs": [], "source": [ "#warnings are preinstalled with anaconda, so we don't have to install it\n", "import warnings\n", "warnings.filterwarnings('ignore')" ] }, { "cell_type": "markdown", "id": "42478e9d", "metadata": {}, "source": [ "## Extra library: NUMBA - to speed up the bot (not necessery for the bot to work properly)" ] }, { "cell_type": "code", "execution_count": null, "id": "8c19ad7c", "metadata": {}, "outputs": [], "source": [ "#!pip install numba --upgrade\n", "#https://pypi.org/project/numba/\n", "\n", "#or if it doesn't work on Windows and you are using Anaconda, \n", "#try writing in Anaconda Prompt: conda install numba" ] }, { "cell_type": "code", "execution_count": null, "id": "b70eb380", "metadata": {}, "outputs": [], "source": [ "#import numba\n", "#from numba import njit" ] }, { "cell_type": "code", "execution_count": null, "id": "b977e2e9", "metadata": {}, "outputs": [], "source": [ "#print(\"numba version: {}\".format(numba.__version__))" ] }, { "cell_type": "markdown", "id": "cbfae93d", "metadata": {}, "source": [ "# CONNECTING TO ALPACA" ] }, { "cell_type": "markdown", "id": "df293339", "metadata": {}, "source": [ "## URLs to use" ] }, { "cell_type": "markdown", "id": "eecaf566", "metadata": {}, "source": [ "The URLs we use here are for paper trading. If you want to use your bot with your live account, you should change these URLs to those dedicated to live account at Alpaca. Just remember, that with live account you are using real money, so be sure that your bot works as you want it to work. Test your bot before you give it real money to trade.\n", "\n", "If you want to use this bot with your paper account as we do in this tutorial, you can leave these two cells as they are." ] }, { "cell_type": "code", "execution_count": null, "id": "1d3a1f54", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "APCA_API_BASE_URL = \"https://paper-api.alpaca.markets\"\n", "#change if you want to use the bot with your live account" ] }, { "cell_type": "markdown", "id": "5384aee5", "metadata": {}, "source": [ "## Connecting to Alpaca REST api" ] }, { "cell_type": "code", "execution_count": null, "id": "d58fe858", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "api = tradeapi.REST(KEY_ID, SECRET_KEY, APCA_API_BASE_URL, \"v2\")" ] }, { "cell_type": "code", "execution_count": null, "id": "1dfaab1e", "metadata": {}, "outputs": [], "source": [ "#check if you are connected to Alpaca\n", "nyc_datetime = api.get_clock().timestamp.tz_convert('America/New_York')\n", "print(\"I'm connected to Alpaca and it's {} in New York at the moment. Let's have some fun.\".format(str(nyc_datetime)[:16]))" ] }, { "cell_type": "markdown", "id": "d421b843", "metadata": {}, "source": [ "## Keys to use Alpaca with vectorbt backtesting library" ] }, { "cell_type": "code", "execution_count": null, "id": "c08b32cd", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "vbt.settings.data['alpaca']['key_id'] = KEY_ID\n", "vbt.settings.data['alpaca']['secret_key'] = SECRET_KEY" ] }, { "cell_type": "markdown", "id": "c29758d7", "metadata": {}, "source": [ "## Parameters for downloading and cleaning up the data" ] }, { "cell_type": "code", "execution_count": null, "id": "802fda13", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "data_timeframe = '1m' #replace with preferable time between data: 1m, 5m, 15m, 30m, 1h, 1d\n", "data_limit = None #replace with the limit of the data to download to speed up the process (500, 1000, None)\n", "\n", "crypto_data_timeframe = TimeFrame.Minute\n", "preferred_exchange = \"CBSE\"" ] }, { "cell_type": "code", "execution_count": null, "id": "417b40b0", "metadata": {}, "outputs": [], "source": [ "data_start = '2022-05-30' #replace with the starting point for collecting data\n", "data_end = '2022-06-06' #replace with the ending point for collecting the data" ] }, { "cell_type": "markdown", "id": "ffebb73e", "metadata": {}, "source": [ "## Downloading and preparing the data for the SuperAI Trading Bot in one function" ] }, { "cell_type": "code", "execution_count": null, "id": "bb61fa0a", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#preparing data in one function\n", "\n", "def prepare_data(start_date, end_date):\n", " data_start = start_date\n", " data_end = end_date\n", " \n", " if asset_type == \"stock\" and data_source == \"Alpaca\":\n", " full_data = vbt.AlpacaData.download(asset, start=data_start, end=data_end, \n", " timeframe=data_timeframe, limit=data_limit).get()\n", " \n", " elif asset_type == \"stock\" and data_source == \"Yahoo\":\n", " full_data = vbt.YFData.download(asset, start = data_start, end= data_end, \n", " interval=data_timeframe).get().drop([\"Dividends\", \"Stock Splits\"], axis=1)\n", " \n", " elif asset_type == \"crypto\":\n", " crypto_data = api.get_crypto_bars(asset, crypto_data_timeframe, start = data_start, end=data_end).df\n", " full_crypto_data = crypto_data[crypto_data['exchange'] == preferred_exchange]\n", " full_data = full_crypto_data.rename(str.capitalize, axis=1).drop([\"Exchange\", \"Trade_count\", \"Vwap\"], axis=1)\n", " \n", " else:\n", " print(\"You have to declare asset type as crypto or stock for me to work properly.\")\n", " \n", " full_data.index = full_data.index.tz_convert('America/New_York')\n", " \n", " (trading_hour_start, trading_hour_stop, \n", " buyless_time_start_1, buyless_time_end_1, buyless_time_start_2, buyless_time_end_2,\n", " selltime_start, selltime_end) = trading_buy_sell_time()\n", " \n", " data = full_data.copy()\n", " data = data.between_time(trading_hour_start, trading_hour_stop)\n", " \n", " not_time_to_buy_1 = data.index.indexer_between_time(buyless_time_start_1, buyless_time_end_1) \n", " not_time_to_buy_2 = data.index.indexer_between_time(buyless_time_start_2, buyless_time_end_2) \n", " not_time_to_buy = np.concatenate((not_time_to_buy_1, not_time_to_buy_2), axis=0)\n", " not_time_to_buy = np.unique(not_time_to_buy)\n", " data[\"NotTimeToBuy\"] = 1\n", " data[\"BuylessTime\"] = data.iloc[not_time_to_buy, 5]\n", " data[\"BuylessTime\"] = np.where(np.isnan(data[\"BuylessTime\"]), 0, data[\"BuylessTime\"])\n", " data = data.drop([\"NotTimeToBuy\"], axis=1)\n", "\n", " time_to_sell = data.index.indexer_between_time(selltime_start, selltime_end) \n", " \n", " data[\"TimeToSell\"] = 1\n", " data[\"SellTime\"] = data.iloc[time_to_sell, 6]\n", " data[\"SellTime\"] = np.where(np.isnan(data[\"SellTime\"]), 0, data[\"SellTime\"])\n", " data = data.drop([\"TimeToSell\"], axis=1)\n", " \n", " open_prices = data[\"Open\"]\n", " high_prices = data[\"High\"]\n", " low_prices = data[\"Low\"]\n", " close_prices = data[\"Close\"]\n", " volume = data[\"Volume\"]\n", " buylesstime = data[\"BuylessTime\"]\n", " selltime = data[\"SellTime\"]\n", " \n", " return open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime\n", "\n", "#open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime = prepare_data(data_start, data_end)" ] }, { "cell_type": "markdown", "id": "460e68eb", "metadata": {}, "source": [ "## Extra Explanation: Downloading, cleaning up, and visualizing data from Alpaca (1-min intervals)" ] }, { "cell_type": "code", "execution_count": null, "id": "e5e69e64", "metadata": {}, "outputs": [], "source": [ "#downloading stock data with vbt from Alpaca or Yahoo: https://vectorbt.dev/api/data/custom/ \n", "#downloading crypto data directly from Alpaca: https://alpaca.markets/learn/getting-started-with-alpaca-crypto-api/\n", "\n", "if asset_type == \"stock\" and data_source == \"Alpaca\":\n", " full_data = vbt.AlpacaData.download(asset, start=data_start, end=data_end, timeframe=data_timeframe, limit=data_limit).get()\n", " \n", "elif asset_type == \"stock\" and data_source == \"Yahoo\":\n", " full_data = vbt.YFData.download(asset, start = data_start, end= data_end, interval=data_timeframe).get().drop(\n", " [\"Dividends\", \"Stock Splits\"], axis=1)\n", " \n", "elif asset_type == \"crypto\":\n", " crypto_data = api.get_crypto_bars(asset, crypto_data_timeframe, start = data_start, end=data_end).df\n", " full_crypto_data = crypto_data[crypto_data['exchange'] == preferred_exchange]\n", " full_data = full_crypto_data.rename(str.capitalize, axis=1).drop([\"Exchange\", \"Trade_count\", \"Vwap\"], axis=1)\n", " \n", "full_data" ] }, { "cell_type": "code", "execution_count": null, "id": "69db72a5", "metadata": {}, "outputs": [], "source": [ "full_data.index = full_data.index.tz_convert('America/New_York')\n", "\n", "full_data" ] }, { "cell_type": "code", "execution_count": null, "id": "2a008b61", "metadata": {}, "outputs": [], "source": [ "full_data.vbt.ohlcv.plot()" ] }, { "cell_type": "code", "execution_count": null, "id": "c2b2393a", "metadata": {}, "outputs": [], "source": [ "data = full_data.copy()\n", "data = data.between_time(trading_hour_start, trading_hour_stop)\n", "\n", "data" ] }, { "cell_type": "code", "execution_count": null, "id": "d4510303", "metadata": {}, "outputs": [], "source": [ "data.vbt.ohlcv.plot()" ] }, { "cell_type": "code", "execution_count": null, "id": "dcfc9df7", "metadata": {}, "outputs": [], "source": [ "not_time_to_buy_1 = data.index.indexer_between_time(buyless_time_start_1, buyless_time_end_1) \n", "not_time_to_buy_2 = data.index.indexer_between_time(buyless_time_start_2, buyless_time_end_2) \n", "not_time_to_buy = np.concatenate((not_time_to_buy_1, not_time_to_buy_2), axis=0)\n", "not_time_to_buy = np.unique(not_time_to_buy)\n", "\n", "not_time_to_buy" ] }, { "cell_type": "code", "execution_count": null, "id": "17f6a65f", "metadata": {}, "outputs": [], "source": [ "data[\"NotTimeToBuy\"] = 1\n", "data[\"BuylessTime\"] = data.iloc[not_time_to_buy, 5]\n", "data[\"BuylessTime\"] = np.where(np.isnan(data[\"BuylessTime\"]), 0, data[\"BuylessTime\"])\n", "data = data.drop([\"NotTimeToBuy\"], axis=1)\n", "\n", "data" ] }, { "cell_type": "code", "execution_count": null, "id": "e9d80fcc", "metadata": {}, "outputs": [], "source": [ "time_to_sell = data.index.indexer_between_time(selltime_start, selltime_end) \n", "\n", "time_to_sell" ] }, { "cell_type": "code", "execution_count": null, "id": "2c53a3ea", "metadata": {}, "outputs": [], "source": [ "data[\"TimeToSell\"] = 1\n", "data[\"SellTime\"] = data.iloc[time_to_sell, 6]\n", "data[\"SellTime\"] = np.where(np.isnan(data[\"SellTime\"]), 0, data[\"SellTime\"])\n", "data = data.drop([\"TimeToSell\"], axis=1)\n", "\n", "data" ] }, { "cell_type": "code", "execution_count": null, "id": "b73ce40b", "metadata": {}, "outputs": [], "source": [ "open_prices = data[\"Open\"]\n", "high_prices = data[\"High\"]\n", "low_prices = data[\"Low\"]\n", "close_prices = data[\"Close\"]\n", "volume = data[\"Volume\"]\n", "buylesstime = data[\"BuylessTime\"]\n", "selltime = data[\"SellTime\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "c1dd062f", "metadata": {}, "outputs": [], "source": [ "print(buylesstime.to_string())" ] }, { "cell_type": "markdown", "id": "6d75826c", "metadata": {}, "source": [ "# Extra explanation: Technical Indicators Basics (Trend, Momentum, Volatility, Volume) and how we can use them" ] }, { "cell_type": "markdown", "id": "32731df7", "metadata": {}, "source": [ "## TREND: Moving Average" ] }, { "cell_type": "markdown", "id": "6a0604aa", "metadata": {}, "source": [ "There are different types of MA (SMA, WMA, EMA)\n", "\n", "You can read more at Investopedia: \n", "- Moving Average (MA) https://www.investopedia.com/terms/m/movingaverage.asp\n", "- How to Use a Moving Average to Buy Stocks https://www.investopedia.com/articles/active-trading/052014/how-use-moving-average-buy-stocks.asp" ] }, { "cell_type": "code", "execution_count": null, "id": "f39f1733", "metadata": {}, "outputs": [], "source": [ "# TREND\n", "#TREND: SMA Simple Moving Average\n", "data['sma'] = ta.trend.sma_indicator(close_prices, window =14)\n", "\n", "#TREND: WMA Weighted Moving Average\n", "data['wma'] = ta.trend.wma_indicator(close_prices, window = 14)\n", "\n", "#TREND: EMA Exponential Moving Average (with different time windows)\n", "data['ema'] = ta.trend.ema_indicator(close_prices, window = 28)\n", "data['ema_fast'] = ta.trend.ema_indicator(close_prices, window = 14)\n", "data['ema_slow'] = ta.trend.ema_indicator(close_prices, window = 50)\n", "\n", "data.iloc[10:20, 3:]" ] }, { "cell_type": "markdown", "id": "4d8478d5", "metadata": {}, "source": [ "### *Cross-over strategies*" ] }, { "cell_type": "markdown", "id": "d8053153", "metadata": {}, "source": [ "*(close_prices < ma) & (shifted(close_prices, 1) > shifted(ma,1))*" ] }, { "attachments": { "shifted_crossover.jpg": { "image/jpeg": "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" } }, "cell_type": "markdown", "id": "5c9740e6", "metadata": {}, "source": [ "![shifted_crossover.jpg](attachment:shifted_crossover.jpg)" ] }, { "cell_type": "code", "execution_count": null, "id": "0f439962", "metadata": {}, "outputs": [], "source": [ "data['close_shifted'] = shifted(close_prices, 1)\n", "data['ma shifted'] = shifted(data['sma'], 1)\n", "\n", "data.iloc[:20, 3:]" ] }, { "cell_type": "code", "execution_count": null, "id": "d2f36986", "metadata": {}, "outputs": [], "source": [ "data = data.drop([\"ma shifted\"], axis=1)" ] }, { "cell_type": "markdown", "id": "2dcbba74", "metadata": {}, "source": [ "*(close_prices < ma) & (shifted(close_prices, 1) > shifted(ma, 1))*" ] }, { "cell_type": "code", "execution_count": null, "id": "8aa6be1b", "metadata": {}, "outputs": [], "source": [ "vbt.MA.run(data.iloc[:90]['Close'], 14).plot()" ] }, { "cell_type": "markdown", "id": "19295b8e", "metadata": {}, "source": [ "## TREND: MACD" ] }, { "cell_type": "markdown", "id": "73a80cf8", "metadata": {}, "source": [ "You can read more about Moving Average Convergence Divergence (MACD) here: https://www.investopedia.com/terms/m/macd.asp" ] }, { "cell_type": "code", "execution_count": null, "id": "47dac7ed", "metadata": {}, "outputs": [], "source": [ "#TREND: MACD (Moving Average Convergence Divergence)\n", "data['macd'] = ta.trend.macd(close_prices, window_slow = 26, window_fast = 12)\n", "data['macd_diff'] = ta.trend.macd_diff(close_prices, window_slow = 26, window_fast = 12, window_sign = 9)\n", "data['macd_signal'] = ta.trend.macd_signal(close_prices, window_slow = 26, window_fast = 12, window_sign = 9)\n", "\n", "data.iloc[20:35, 12:]" ] }, { "cell_type": "markdown", "id": "523e1678", "metadata": {}, "source": [ "### *One bigger than other, threshold strategies*" ] }, { "cell_type": "markdown", "id": "8846512f", "metadata": {}, "source": [ "*(macd < 0) | (macd_diff < 0) | (macd_sign < macd)*" ] }, { "cell_type": "code", "execution_count": null, "id": "17619b07", "metadata": {}, "outputs": [], "source": [ "vbt.MACD.run(data.iloc[:90]['Close'], 14).plot()" ] }, { "cell_type": "markdown", "id": "b4eea663", "metadata": {}, "source": [ "## MOMENTUM: RSI and Stochastic" ] }, { "cell_type": "markdown", "id": "d6f95613", "metadata": {}, "source": [ "You can read more about:\n", "\n", "RSI: https://www.investopedia.com/terms/r/rsi.asp\n", "\n", "Stochastic: https://www.investopedia.com/terms/s/stochasticoscillator.asp" ] }, { "cell_type": "code", "execution_count": null, "id": "df191e4f", "metadata": {}, "outputs": [], "source": [ "# MOMENTUM: RSI (Relative Strength Index)\n", "data['rsi'] = ta.momentum.rsi(close_prices, window = 14)\n", "\n", "# MOMENTUM: Stoch (Stochastic)\n", "data['stoch'] = ta.momentum.stoch(high_prices, low_prices, close_prices, window = 14, smooth_window = 3)\n", "data['stoch_signal'] = ta.momentum.stoch_signal(high_prices, low_prices, close_prices, window=14, smooth_window=3)\n", "\n", "data.iloc[10:20, 15:]" ] }, { "cell_type": "markdown", "id": "24cbd938", "metadata": {}, "source": [ "### *Thresholds strategies (e.g. oversold / overbought strategies)*" ] }, { "cell_type": "markdown", "id": "8800f730", "metadata": {}, "source": [ "*(rsi < rsi_entry)*" ] }, { "cell_type": "code", "execution_count": null, "id": "36f684eb", "metadata": {}, "outputs": [], "source": [ "vbt.RSI.run(data.iloc[:90]['Close']).plot()" ] }, { "cell_type": "markdown", "id": "3dd711e5", "metadata": {}, "source": [ "*(stoch < stoch_entry) | (stoch_signal < stoch_entry)*" ] }, { "cell_type": "code", "execution_count": null, "id": "58897fc5", "metadata": {}, "outputs": [], "source": [ "vbt.STOCH.run(data['High'][:90], data['Low'][:90], data['Close'][:90]).plot()" ] }, { "cell_type": "markdown", "id": "ebceef5f", "metadata": {}, "source": [ "### *Differentiating strategies (e.g. different strategy for the up-trend, down-trend, etc.)*" ] }, { "cell_type": "markdown", "id": "7a2a69c5", "metadata": {}, "source": [ "*((stoch < stoch_entry) & ((ma > (shifted(ma, 1))) & (shifted(ma, 1) > (shifted(ma, 2)))))*" ] }, { "cell_type": "code", "execution_count": null, "id": "600a2ee0", "metadata": {}, "outputs": [], "source": [ "data['ema_not_shifted'] = data['ema_fast']\n", "data['ema_sh_1'] = shifted(data['ema_fast'], 1)\n", "data['ema_sh_2'] = shifted(data['ema_fast'], 2)\n", "data.iloc[10:25, 16:]" ] }, { "cell_type": "markdown", "id": "32e747a4", "metadata": {}, "source": [ "*((ma > (shifted(ma, 1))) & (shifted(ma, 1) > (shifted(ma, 2))))*" ] }, { "cell_type": "code", "execution_count": null, "id": "3f8fda17", "metadata": {}, "outputs": [], "source": [ "vbt.MA.run(data.iloc[:90]['Close'], 14).plot()" ] }, { "cell_type": "markdown", "id": "a33083fe", "metadata": {}, "source": [ "*((rsi < rsi_entry) & (~((ma > (shifted(ma, 2) * 1.01)) & (shifted(ma, 2) > (shifted(ma, 4)*1.01)))))*" ] }, { "cell_type": "code", "execution_count": null, "id": "edd6c753", "metadata": {}, "outputs": [], "source": [ "data['ema_sh_5_101'] = shifted(data['ema_fast'], 2)*1.01\n", "data['ema_sh_10_101'] = shifted(data['ema_fast'], 4)*1.01\n", "data.iloc[10:20, 16:]" ] }, { "cell_type": "markdown", "id": "2278dc03", "metadata": {}, "source": [ "### Extra Explanation: RSI != RSI \n", "(why You should always use libraries calculating technical indicators with caution)\n", "\n", "How RSI is calculated? https://www.investopedia.com/terms/r/rsi.asp" ] }, { "cell_type": "code", "execution_count": null, "id": "787b1a08", "metadata": {}, "outputs": [], "source": [ "data_rsi = data.copy()\n", "data_rsi.drop(data_rsi.iloc[:, 5:], inplace = True, axis = 1)\n", "\n", "data_rsi" ] }, { "cell_type": "code", "execution_count": null, "id": "e9f79876", "metadata": {}, "outputs": [], "source": [ "test_close_rsi = data_rsi[\"Close\"].to_numpy()\n", "data_rsi['rsi_vbt'] = vbt.RSI.run(test_close_rsi, 14).rsi.to_numpy()\n", "data_rsi['rsi_talib'] = vbt.IndicatorFactory.from_talib('RSI').run(test_close_rsi, 14).real.to_numpy()\n", "data_rsi['rsi_ta'] = vbt.IndicatorFactory.from_ta('RSIIndicator').run(test_close_rsi, 14).rsi.to_numpy()\n", "\n", "data_rsi.iloc[10:20,:]" ] }, { "cell_type": "markdown", "id": "d0eb98fe", "metadata": {}, "source": [ "## VOLATILITY: Bollinger Bands" ] }, { "cell_type": "markdown", "id": "9407514b", "metadata": {}, "source": [ "You can read more about Bollinger Bands here: https://www.investopedia.com/terms/b/bollingerbands.asp" ] }, { "cell_type": "code", "execution_count": null, "id": "559bd741", "metadata": {}, "outputs": [], "source": [ "# VOLATILITY: BB (Bollinger Bands)\n", "data[\"bb_low\"] = ta.volatility.bollinger_lband(close_prices, window=14, window_dev = 2)\n", "data[\"bb_high\"] = ta.volatility.bollinger_hband(close_prices, window=14, window_dev = 2)\n", "\n", "\n", "data.iloc[10:20, 20:]" ] }, { "cell_type": "code", "execution_count": null, "id": "b6e18563", "metadata": {}, "outputs": [], "source": [ "vbt.BBANDS.run(data['Close'][:90]).plot()" ] }, { "cell_type": "markdown", "id": "8947e6ed", "metadata": {}, "source": [ "## VOLUME: Money Flow Index" ] }, { "cell_type": "markdown", "id": "dc4d980e", "metadata": {}, "source": [ "You can read more about MFI here: https://www.investopedia.com/terms/m/mfi.asp" ] }, { "cell_type": "code", "execution_count": null, "id": "6cb1a982", "metadata": {}, "outputs": [], "source": [ "# VOLUME: MFI (Money Flow Index (='volume-weighted RSI'))\n", "data['mfi'] = ta.volume.money_flow_index(high_prices, low_prices, close_prices, volume, window = 14)\n", "\n", "data.iloc[10:20, 24:]" ] }, { "cell_type": "code", "execution_count": null, "id": "e4fc18cb", "metadata": {}, "outputs": [], "source": [ "px.line(data['mfi'][:90], title='Money Flow Index')" ] }, { "cell_type": "markdown", "id": "5396ed8e", "metadata": {}, "source": [ "## CandleStick Patterns" ] }, { "cell_type": "markdown", "id": "5e307414", "metadata": {}, "source": [ "You can read more about candlestick patterns here: \n", "- Candlestick Definition https://www.investopedia.com/terms/c/candlestick.asp\n", "- Understanding Basic Candlestick Charts https://www.investopedia.com/trading/candlestick-charting-what-is-it/\n", "- The 5 Most Powerful Candlestick Patterns https://www.investopedia.com/articles/active-trading/092315/5-most-powerful-candlestick-patterns.asp" ] }, { "cell_type": "code", "execution_count": null, "id": "95356ae5", "metadata": {}, "outputs": [], "source": [ "# CandleStick Patterns Signals\n", "\n", "# candle buy signal 'Hammer'\n", "data[\"c_buy_1\"] = ta_lib.CDLHAMMER(open_prices, high_prices, low_prices, close_prices) \n", "\n", "# candle buy signal 'Morning Star'\n", "data[\"c_buy_2\"] = ta_lib.CDLMORNINGSTAR(open_prices, high_prices, low_prices, close_prices)\n", "\n", "# candle buy signal '3 White Soldiers'\n", "data[\"c_buy_3\"] = ta_lib.CDL3WHITESOLDIERS(open_prices, high_prices, low_prices, close_prices) \n", "\n", "# candle sell signal 'Shooting Star'\n", "data[\"c_sell_1\"] = ta_lib.CDLSHOOTINGSTAR(open_prices, high_prices, low_prices, close_prices) \n", "\n", "# candle sell signal 'Evening Star'\n", "data[\"c_sell_2\"] = ta_lib.CDLEVENINGSTAR(open_prices, high_prices, low_prices, close_prices) \n", "\n", "# candle sell signal '3 Black Crows'\n", "data[\"c_sell_3\"] = ta_lib.CDL3BLACKCROWS(open_prices, high_prices, low_prices, close_prices) \n", "\n", "# candle buy/sell signal 'Engulfing Bullish / Bearish'\n", "data[\"c_bs_1\"] = ta_lib.CDLENGULFING(open_prices, high_prices, low_prices, close_prices) \n", "\n", "# candle buy/sell signal '3 Outside Up / Down'\n", "data[\"c_bs_2\"] = ta_lib.CDL3OUTSIDE(open_prices, high_prices, low_prices, close_prices) \n", "\n", "data.iloc[30:50, 26:]" ] }, { "cell_type": "markdown", "id": "f05b32cc", "metadata": {}, "source": [ "### *Candlestick Patterns Strategies*" ] }, { "cell_type": "markdown", "id": "07874cf3", "metadata": {}, "source": [ "*((candle_buy_signal_1 > 0) | (candle_buy_signal_2 > 0) | (candle_buy_signal_3 > 0) | (candle_buy_sell_signal_1 > 0) | (candle_buy_sell_signal_2 > 0))*" ] }, { "cell_type": "code", "execution_count": null, "id": "17727e4a", "metadata": {}, "outputs": [], "source": [ "go.Figure(data=[go.Candlestick(x=data.index[10:70],\n", " open=data['Open'][10:70],\n", " high=data['High'][10:70],\n", " low=data['Low'][10:70],\n", " close=data['Close'][10:70])])" ] }, { "cell_type": "markdown", "id": "552270f6", "metadata": {}, "source": [ "# BACKTESTING WITH TRAINING AND VALIDATION SET" ] }, { "cell_type": "markdown", "id": "a813c820", "metadata": {}, "source": [ "# Custom SuperAI Indicator" ] }, { "cell_type": "markdown", "id": "a20831fa", "metadata": {}, "source": [ "## Signals Function" ] }, { "cell_type": "code", "execution_count": null, "id": "d20ed7af", "metadata": {}, "outputs": [], "source": [ "def superai_signals (open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime,\n", " ma_window = ma_timeframe, \n", " ma_fast_window = ma_fast_timeframe,\n", " ma_slow_window = ma_slow_timeframe,\n", " macd_slow_window = macd_slow_timeframe, \n", " macd_fast_window = macd_fast_timeframe, \n", " macd_sign_window = macd_signal_timeframe,\n", " rsi_window = rsi_timeframe,\n", " rsi_entry = rsi_oversold_threshold, \n", " rsi_exit = rsi_overbought_threshold, \n", " stoch_window = stoch_timeframe,\n", " stoch_smooth_window = stoch_smooth_timeframe,\n", " stoch_entry = stoch_oversold_threshold, \n", " stoch_exit = stoch_overbought_threshold,\n", " bb_window = bb_timeframe,\n", " bb_dev = bb_dev,\n", " mfi_window = mfi_timeframe,\n", " mfi_entry = mfi_oversold_threshold,\n", " mfi_exit = mfi_overbought_threshold):\n", " \n", " rsi = vbt.IndicatorFactory.from_ta('RSIIndicator').run(close_prices, window = rsi_window).rsi.to_numpy()\n", " \n", " stoch = vbt.IndicatorFactory.from_ta('StochasticOscillator').run(\n", " high_prices, low_prices, close_prices, window = stoch_window, smooth_window = stoch_smooth_window).stoch.to_numpy()\n", " stoch_signal = vbt.IndicatorFactory.from_ta('StochasticOscillator').run(\n", " high_prices, low_prices, close_prices, window = stoch_window, \n", " smooth_window = stoch_smooth_window).stoch_signal.to_numpy()\n", " \n", " ma = vbt.IndicatorFactory.from_ta('EMAIndicator').run(close_prices, window = ma_window).ema_indicator.to_numpy()\n", " ma_fast = vbt.IndicatorFactory.from_ta('EMAIndicator').run(close_prices, window = ma_fast_window).ema_indicator.to_numpy()\n", " ma_slow = vbt.IndicatorFactory.from_ta('EMAIndicator').run(close_prices, window = ma_slow_window).ema_indicator.to_numpy()\n", " \n", " macd = vbt.IndicatorFactory.from_ta('MACD').run(\n", " close_prices, window_slow = macd_slow_window, window_fast = macd_fast_window, \n", " window_sign = macd_sign_window).macd.to_numpy()\n", " macd_diff = vbt.IndicatorFactory.from_ta('MACD').run(\n", " close_prices, macd_slow_window, window_fast = macd_fast_window, \n", " window_sign = macd_sign_window).macd_diff.to_numpy()\n", " macd_sign = vbt.IndicatorFactory.from_ta('MACD').run(\n", " close_prices, macd_slow_window, window_fast = macd_fast_window, \n", " window_sign = macd_sign_window).macd_signal.to_numpy()\n", "\n", " bb_low = vbt.IndicatorFactory.from_ta('BollingerBands').run(\n", " close_prices, window = bb_window, window_dev = bb_dev).bollinger_lband.to_numpy()\n", " bb_high = vbt.IndicatorFactory.from_ta('BollingerBands').run(\n", " close_prices, window = bb_window, window_dev = bb_dev).bollinger_hband.to_numpy()\n", " \n", " mfi = vbt.IndicatorFactory.from_ta('MFIIndicator').run(\n", " high_prices, low_prices, close_prices, volume, window = mfi_timeframe).money_flow_index.to_numpy()\n", " \n", " candle_buy_signal_1 = vbt.IndicatorFactory.from_talib('CDLHAMMER').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Hammer'\n", " candle_buy_signal_2 = vbt.IndicatorFactory.from_talib('CDLMORNINGSTAR').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Morning star'\n", " candle_buy_signal_3 = vbt.IndicatorFactory.from_talib('CDL3WHITESOLDIERS').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Three White Soldiers'\n", " candle_sell_signal_1 = vbt.IndicatorFactory.from_talib('CDLSHOOTINGSTAR').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Shooting star' \n", " candle_sell_signal_2 = vbt.IndicatorFactory.from_talib('CDLEVENINGSTAR').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Evening star'\n", " candle_sell_signal_3 = vbt.IndicatorFactory.from_talib('CDL3BLACKCROWS').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # '3 Black Crows'\n", " candle_buy_sell_signal_1 = vbt.IndicatorFactory.from_talib('CDLENGULFING').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Engulfing: Bullish (buy) / Bearish (sell)'\n", " candle_buy_sell_signal_2 = vbt.IndicatorFactory.from_talib('CDL3OUTSIDE').run(\n", " open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Three Outside: Up (buy) / Down (sell)'\n", " \n", " SuperAI_signal = create_signal(open_prices, high_prices, low_prices, close_prices, volume, \n", " buylesstime, selltime, \n", " ma, ma_fast, ma_slow,\n", " macd, macd_diff, macd_sign,\n", " rsi, rsi_entry, rsi_exit, \n", " stoch, stoch_signal, stoch_entry, stoch_exit,\n", " bb_low, bb_high, \n", " mfi, mfi_entry, mfi_exit,\n", " candle_buy_signal_1, candle_buy_signal_2, candle_buy_signal_3,\n", " candle_sell_signal_1, candle_sell_signal_2, candle_sell_signal_3,\n", " candle_buy_sell_signal_1, candle_buy_sell_signal_2)\n", " return SuperAI_signal" ] }, { "cell_type": "markdown", "id": "8f3e50f8", "metadata": {}, "source": [ "## Parameters to optimize" ] }, { "cell_type": "code", "execution_count": null, "id": "d91aadcf", "metadata": {}, "outputs": [], "source": [ "parameters_names = [\"ma_window\", \"ma_fast_window\", \"ma_slow_window\", \n", " \"macd_slow_window\", \"macd_fast_window\", \"macd_sign_window\", \n", " \"rsi_window\", \"rsi_entry\", \"rsi_exit\",\n", " \"stoch_window\", \"stoch_smooth_window\", \"stoch_entry\", \"stoch_exit\",\n", " \"bb_window\", \"bb_dev\", \n", " \"mfi_window\", \"mfi_entry\", \"mfi_exit\"]" ] }, { "cell_type": "markdown", "id": "3ccca269", "metadata": {}, "source": [ "## Indicator" ] }, { "cell_type": "code", "execution_count": null, "id": "777c7f6a", "metadata": {}, "outputs": [], "source": [ "SuperAI_Ind = vbt.IndicatorFactory(\n", " class_name = \"SuperAI_Ind\",\n", " short_name = \"SuperInd\",\n", " input_names = [\"open\", \"high\", \"low\", \"close\", \"volume\", \"buylesstime\", \"selltime\"],\n", " param_names = parameters_names,\n", " output_names = [\"output\"]).from_apply_func(superai_signals,\n", " ma_window = ma_timeframe,\n", " ma_fast_window = ma_fast_timeframe,\n", " ma_slow_window = ma_slow_timeframe, \n", " \n", " macd_slow_window = macd_slow_timeframe, \n", " macd_fast_window = macd_fast_timeframe,\n", " macd_sign_window = macd_signal_timeframe,\n", " \n", " rsi_window = rsi_timeframe,\n", " rsi_entry = rsi_oversold_threshold, \n", " rsi_exit = rsi_overbought_threshold,\n", " \n", " stoch_window = stoch_timeframe,\n", " stoch_smooth_window = stoch_smooth_timeframe,\n", " stoch_entry = stoch_oversold_threshold, \n", " stoch_exit = stoch_overbought_threshold,\n", " \n", " bb_window = bb_timeframe,\n", " bb_dev = bb_dev,\n", " \n", " mfi_window = mfi_timeframe,\n", " mfi_entry = mfi_oversold_threshold, \n", " mfi_exit = mfi_overbought_threshold)" ] }, { "cell_type": "markdown", "id": "9de4cbbb", "metadata": {}, "source": [ "# TESTING THE PROTOTYPE" ] }, { "cell_type": "code", "execution_count": null, "id": "039d2411", "metadata": {}, "outputs": [], "source": [ "open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime = prepare_data(data_start, data_end)" ] }, { "cell_type": "code", "execution_count": null, "id": "e11da5b5", "metadata": {}, "outputs": [], "source": [ "trading_signals = SuperAI_Ind.run(open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime,\n", " \n", " ma_window = ma_timeframe,\n", " ma_fast_window = ma_fast_timeframe,\n", " ma_slow_window = ma_slow_timeframe,\n", " \n", " macd_slow_window = macd_slow_timeframe,\n", " macd_fast_window = macd_fast_timeframe,\n", " macd_sign_window = macd_signal_timeframe,\n", " \n", " rsi_window = rsi_timeframe,\n", " rsi_entry = rsi_oversold_threshold,\n", " rsi_exit= rsi_overbought_threshold,\n", " \n", " stoch_window = stoch_timeframe,\n", " stoch_smooth_window = stoch_smooth_timeframe,\n", " stoch_entry = stoch_oversold_threshold, \n", " stoch_exit = stoch_overbought_threshold,\n", " \n", " bb_window = bb_timeframe,\n", " bb_dev = bb_dev,\n", " \n", " mfi_window = mfi_timeframe,\n", " mfi_entry = mfi_oversold_threshold,\n", " mfi_exit= mfi_overbought_threshold,\n", " \n", " param_product = True)" ] }, { "cell_type": "code", "execution_count": null, "id": "5daf0df2", "metadata": {}, "outputs": [], "source": [ "print(trading_signals.output.to_string())" ] }, { "cell_type": "code", "execution_count": null, "id": "fc377e86", "metadata": {}, "outputs": [], "source": [ "entries = trading_signals.output == 1.0\n", "exits = trading_signals.output == -1.0" ] }, { "cell_type": "code", "execution_count": null, "id": "94680bb4", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio = vbt.Portfolio.from_signals(close_prices, \n", " entries, \n", " exits, \n", " init_cash = 100000, \n", " tp_stop = take_profit_percent,\n", " sl_stop = stop_loss_percent,\n", " fees = 0.00)" ] }, { "cell_type": "code", "execution_count": null, "id": "58d35ae4", "metadata": {}, "outputs": [], "source": [ "returns = SuperAI_portfolio.total_return() * 100\n", "returns" ] }, { "cell_type": "code", "execution_count": null, "id": "5792a51d", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio.plot()" ] }, { "cell_type": "code", "execution_count": null, "id": "9ffb0018", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio.stats()" ] }, { "cell_type": "markdown", "id": "c7ddab54", "metadata": {}, "source": [ "# OPTIMIZING THE BOT WITH A GRID OF DIFFERENT POSSIBILITIES FOR PREFERRED PARAMETER" ] }, { "cell_type": "code", "execution_count": null, "id": "872a6e15", "metadata": {}, "outputs": [], "source": [ "trading_signals = SuperAI_Ind.run(open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime,\n", " \n", " ma_window = np.arange(14, 30, step=14, dtype=int), #[14, 28]\n", " ma_fast_window = np.arange(14, 22, step=7, dtype=int), #[14, 21]\n", " ma_slow_window = np.arange(30, 51, step=20, dtype=int), #[30, 50]\n", " \n", " macd_slow_window = np.arange(26, 27, step=100, dtype=int), #[26]\n", " macd_fast_window = np.arange(12, 13, step=100, dtype=int), #[12]\n", " macd_sign_window = np.arange(9, 10, step=100, dtype=int), #[9]\n", " \n", " rsi_window = np.arange(14, 22, step=7, dtype=int), #[14, 21]\n", " rsi_entry = np.arange(20, 31, step=10, dtype=int), #[20, 30]\n", " rsi_exit= np.arange(70, 81, step=10, dtype=int), #[70, 80]\n", " \n", " stoch_window = np.arange(14, 15, step=100, dtype=int), #[14]\n", " stoch_smooth_window = np.arange(3, 4, step=100, dtype=int), #[3]\n", " stoch_entry = np.arange(20, 21, step=100, dtype=int), #[20]\n", " stoch_exit= np.arange(80, 81, step=100, dtype=int), #[80]\n", " \n", " bb_window = np.arange(10, 21, step=10, dtype=int), #[10, 20]\n", " bb_dev = np.arange(2, 3, step=100, dtype=int), #[2]\n", " \n", " mfi_window = np.arange(14, 22, step=7, dtype=int), #[14, 21]\n", " mfi_entry = np.arange(10, 21, step=10, dtype=int), #[10, 20]\n", " mfi_exit= np.arange(80, 91, step=10, dtype=int), #[80, 90]\n", " \n", " param_product = True)" ] }, { "cell_type": "code", "execution_count": null, "id": "7e9b8e5e", "metadata": {}, "outputs": [], "source": [ "entries = trading_signals.output == 1.0\n", "exits = trading_signals.output == -1.0" ] }, { "cell_type": "code", "execution_count": null, "id": "6ce6bbdb", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio = vbt.Portfolio.from_signals(close_prices, \n", " entries, \n", " exits, \n", " init_cash = 100000, \n", " tp_stop = take_profit_percent,\n", " sl_stop = stop_loss_percent,\n", " fees = 0.00)" ] }, { "cell_type": "code", "execution_count": null, "id": "f8f691c3", "metadata": {}, "outputs": [], "source": [ "returns = SuperAI_portfolio.total_return() * 100" ] }, { "cell_type": "code", "execution_count": null, "id": "77b05811", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio.stats()" ] }, { "cell_type": "code", "execution_count": null, "id": "fae84b3a", "metadata": {}, "outputs": [], "source": [ "returns" ] }, { "cell_type": "code", "execution_count": null, "id": "7fd2670c", "metadata": {}, "outputs": [], "source": [ "returns.max()" ] }, { "cell_type": "code", "execution_count": null, "id": "c69ec7e7", "metadata": {}, "outputs": [], "source": [ "max_dd = SuperAI_portfolio.max_drawdown()\n", "max_dd.max()" ] }, { "cell_type": "code", "execution_count": null, "id": "42e99903", "metadata": {}, "outputs": [], "source": [ "sharpe_ratio = SuperAI_portfolio.sharpe_ratio(freq='m')\n", "sharpe_ratio.max()" ] }, { "cell_type": "markdown", "id": "3b5dd751", "metadata": {}, "source": [ "# APPLYING THE BEST PARAMETERS TO THE MODEL" ] }, { "cell_type": "code", "execution_count": null, "id": "e713cbb4", "metadata": {}, "outputs": [], "source": [ "returns.idxmax()" ] }, { "cell_type": "code", "execution_count": null, "id": "824fa1bb", "metadata": {}, "outputs": [], "source": [ "max_dd.idxmax()" ] }, { "cell_type": "code", "execution_count": null, "id": "7c983954", "metadata": {}, "outputs": [], "source": [ "sharpe_ratio.idxmax()" ] }, { "cell_type": "code", "execution_count": null, "id": "b9c021f6", "metadata": {}, "outputs": [], "source": [ "(ma_window_max, ma_fast_window_max, ma_slow_window_max, \n", " macd_slow_window_max, macd_fast_window_max, macd_sign_window_max,\n", " rsi_window_max, rsi_entry_max, rsi_exit_max, \n", " stoch_window_max, stoch_smooth_window_max, stoch_entry_max, stoch_exit_max,\n", " bb_window_max, bb_dev_max, mfi_window_max, \n", " mfi_entry_max, mfi_exit_max) = returns.idxmax() #max_dd.idxmax() #sharpe_ratio.idxmax()" ] }, { "cell_type": "code", "execution_count": null, "id": "4521e3c7", "metadata": {}, "outputs": [], "source": [ "(ma_window_max, ma_fast_window_max, ma_slow_window_max, \n", " macd_slow_window_max, macd_fast_window_max, macd_sign_window_max,\n", " rsi_window_max, rsi_entry_max, rsi_exit_max, \n", " stoch_window_max, stoch_smooth_window_max, stoch_entry_max, stoch_exit_max,\n", " bb_window_max, bb_dev_max, mfi_window_max, \n", " mfi_entry_max, mfi_exit_max)" ] }, { "cell_type": "code", "execution_count": null, "id": "414373b1", "metadata": {}, "outputs": [], "source": [ "trading_signals = SuperAI_Ind.run(open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime,\n", " \n", " ma_window = ma_window_max,\n", " ma_fast_window = ma_fast_window_max,\n", " ma_slow_window = ma_slow_window_max,\n", " \n", " macd_slow_window = macd_slow_window_max,\n", " macd_fast_window = macd_fast_window_max,\n", " macd_sign_window = macd_sign_window_max,\n", " \n", " rsi_window = rsi_window_max,\n", " rsi_entry = rsi_entry_max,\n", " rsi_exit= rsi_exit_max,\n", " \n", " stoch_window = stoch_window_max,\n", " stoch_smooth_window = stoch_smooth_window_max,\n", " stoch_entry = stoch_entry_max, \n", " stoch_exit = stoch_exit_max,\n", "\n", " bb_window = bb_window_max,\n", " bb_dev = bb_dev_max,\n", " \n", " mfi_window = mfi_window_max,\n", " mfi_entry = mfi_entry_max,\n", " mfi_exit= mfi_exit_max,\n", " \n", " param_product = True)\n", "\n", "entries = trading_signals.output == 1.0\n", "exits = trading_signals.output == -1.0\n", "\n", "SuperAI_portfolio = vbt.Portfolio.from_signals(close_prices, \n", " entries, \n", " exits, \n", " init_cash = 100000, \n", " tp_stop = take_profit_percent,\n", " sl_stop = stop_loss_percent,\n", " fees = 0.00)" ] }, { "cell_type": "code", "execution_count": null, "id": "13868cd0", "metadata": {}, "outputs": [], "source": [ "returns = SuperAI_portfolio.total_return() * 100\n", "returns" ] }, { "cell_type": "code", "execution_count": null, "id": "5fee172d", "metadata": { "scrolled": true }, "outputs": [], "source": [ "SuperAI_portfolio.stats()" ] }, { "cell_type": "code", "execution_count": null, "id": "450c4e42", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio.plot()" ] }, { "cell_type": "markdown", "id": "587e781a", "metadata": {}, "source": [ "# VALIDATION OF THE MODEL" ] }, { "cell_type": "code", "execution_count": null, "id": "29569680", "metadata": {}, "outputs": [], "source": [ "open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime = prepare_data('2022-06-06', '2022-06-07')" ] }, { "cell_type": "code", "execution_count": null, "id": "9f688c54", "metadata": {}, "outputs": [], "source": [ "trading_signals = SuperAI_Ind.run(open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime,\n", " \n", " ma_window = ma_window_max,\n", " ma_fast_window = ma_fast_window_max,\n", " ma_slow_window = ma_slow_window_max,\n", " \n", " macd_slow_window = macd_slow_window_max,\n", " macd_fast_window = macd_fast_window_max,\n", " macd_sign_window = macd_sign_window_max,\n", " \n", " rsi_window = rsi_window_max,\n", " rsi_entry = rsi_entry_max,\n", " rsi_exit= rsi_exit_max,\n", " \n", " stoch_window = stoch_window_max,\n", " stoch_smooth_window = stoch_smooth_window_max,\n", " stoch_entry = stoch_entry_max, \n", " stoch_exit = stoch_exit_max,\n", " \n", " bb_window = bb_window_max,\n", " bb_dev = bb_dev_max,\n", " \n", " mfi_window = mfi_window_max,\n", " mfi_entry = mfi_entry_max,\n", " mfi_exit= mfi_exit_max,\n", " \n", " param_product = True)\n", "\n", "entries = trading_signals.output == 1.0\n", "exits = trading_signals.output == -1.0\n", "\n", "SuperAI_portfolio = vbt.Portfolio.from_signals(close_prices, \n", " entries, \n", " exits, \n", " init_cash = 100000, \n", " tp_stop = take_profit_percent,\n", " sl_stop = stop_loss_percent,\n", " fees = 0.00)\n", "\n", "SuperAI_portfolio.stats()" ] }, { "cell_type": "code", "execution_count": null, "id": "856398e7", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio.plot()" ] }, { "cell_type": "markdown", "id": "aee7e5d3", "metadata": {}, "source": [ "# LIVE TRADING (WITH PAPER TRADING ACCOUNT)" ] }, { "cell_type": "markdown", "id": "ec926ed6", "metadata": {}, "source": [ "in part 2" ] }, { "cell_type": "markdown", "id": "54dd2fbf", "metadata": {}, "source": [ "# TIPS FOR IMPROVING THE BOT" ] }, { "cell_type": "markdown", "id": "1c274589", "metadata": {}, "source": [ "# Checking a strategy with all calculated indicators (MA, MACD, RSI, Stochastic, BB, MFI) and 8 Candlestick Patterns... and \n", "# Reaching 100% 'WIN RATE'... for a time" ] }, { "cell_type": "code", "execution_count": null, "id": "c61fb2c5", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#(asset, asset_type, rounding) = (\"BTCUSD\", \"crypto\", 0)\n", "(asset, asset_type, data_source) = (\"AAPL\", \"stock\", \"Alpaca\")" ] }, { "cell_type": "code", "execution_count": null, "id": "fd7d1ad3", "metadata": {}, "outputs": [], "source": [ "ma_timeframe = 28\n", "ma_fast_timeframe = 14\n", "ma_slow_timeframe = 50\n", "macd_slow_timeframe = 26\n", "macd_fast_timeframe = 12\n", "macd_signal_timeframe = 9\n", "rsi_timeframe = 14\n", "rsi_oversold_threshold = 30\n", "rsi_overbought_threshold = 70\n", "stoch_timeframe = 14\n", "stoch_smooth_timeframe = 3\n", "stoch_oversold_threshold = 20\n", "stoch_overbought_threshold = 80\n", "bb_timeframe = 10\n", "bb_dev = 2\n", "mfi_timeframe = 14\n", "mfi_oversold_threshold = 20\n", "mfi_overbought_threshold = 80" ] }, { "cell_type": "code", "execution_count": null, "id": "43f56964", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#@njit #you can unhash it if you have numba installed and imported and the bot should work a little faster, if you use njit\n", "def create_signal(open_prices, high_prices, low_prices, close_prices, volume,\n", " buylesstime, selltime, #Time to abstain from buying and forced selling\n", " ma, ma_fast, ma_slow, #Moving Average\n", " macd, macd_diff, macd_sign, #Moving Average Convergence Divergence\n", " rsi, rsi_entry, rsi_exit, #Relative Strength Index\n", " stoch, stoch_signal, stoch_entry, stoch_exit, #Stochastic\n", " bb_low, bb_high, #Bollinger Bands\n", " mfi, mfi_entry, mfi_exit, #Money Flow Index\n", " candle_buy_signal_1, candle_buy_signal_2, candle_buy_signal_3, #Candle signals to buy\n", " candle_sell_signal_1, candle_sell_signal_2, candle_sell_signal_3, #Candle signals to sell\n", " candle_buy_sell_signal_1, candle_buy_sell_signal_2): #Candle signals to buy or sell\n", " \n", " \n", " SuperAI_signal_buy = np.where( \n", " (buylesstime != 1) \n", " &\n", " (\n", " (\n", " ((ma_fast < ma_slow) & (rsi < rsi_entry))\n", " | \n", " (close_prices < bb_low)\n", " | \n", " (mfi < mfi_entry)\n", " | \n", " (close_prices > ma)\n", " | \n", " (macd < 0)\n", " | \n", " (macd_diff < 0)\n", " | \n", " (macd_sign < macd)\n", " | \n", " (stoch < stoch_entry)\n", " | \n", " (stoch_signal < stoch_entry)\n", " | \n", " ((close_prices > ma) & (ma_fast > ma_slow))\n", " | \n", " (((rsi < rsi_entry) & ((close_prices < ma) | (macd < 0))))\n", " | \n", " ((close_prices > ma) & (shifted(close_prices, 1) < ma))\n", " | \n", " ((stoch < stoch_entry) & (\n", " (ma > (shifted(ma, 1))) & \n", " (shifted(ma, 1) > (shifted(ma, 2)))))\n", " | \n", " ((rsi < rsi_entry) & (\n", " ~((ma > (shifted(ma, 2) * 1.01)) & \n", " (shifted(ma, 2) > (shifted(ma, 4)*1.01)))))\n", " )\n", " | \n", " ((candle_buy_signal_1 > 0) | (candle_buy_signal_2 > 0) | (candle_buy_signal_3 > 0)\n", " | (candle_buy_sell_signal_1 > 0) | (candle_buy_sell_signal_2 > 0))\n", " )\n", " , 1, 0) #1 is buy, -1 is sell, 0 is do nothing\n", " SuperAI_signal = np.where( \n", " (selltime == 1)\n", " | \n", " (ma_fast > ma_slow) \n", " | \n", " (rsi > rsi_exit)\n", " | \n", " (close_prices > bb_high)\n", " | \n", " (mfi > mfi_exit)\n", " | \n", " (close_prices < ma)\n", " | \n", " (macd_diff > 0)\n", " | \n", " (macd_sign > macd)\n", " | \n", " (stoch > stoch_exit)\n", " | \n", " (stoch_signal > stoch_exit)\n", " | \n", " ((close_prices < ma) & (rsi > rsi_exit))\n", " | \n", " (((rsi > rsi_exit) & ((close_prices < ma) | (macd > 3))))\n", " | \n", " ((close_prices < ma) & (shifted(close_prices, 1) > ma))\n", " | \n", " ((stoch > stoch_exit) & (\n", " (ma > (shifted(ma, 1))) & \n", " (shifted(ma, 1) > (shifted(ma, 2)))))\n", " | \n", " ((candle_sell_signal_1 < 0) | (candle_sell_signal_2 < 0) | (candle_buy_signal_3 < 0)\n", " | (candle_buy_sell_signal_1 < 0) | (candle_buy_sell_signal_2 < 0))\n", " , -1, SuperAI_signal_buy) #1 is buy, -1 is sell, 0 is do nothing\n", " return SuperAI_signal" ] }, { "cell_type": "code", "execution_count": null, "id": "1a0672f7", "metadata": {}, "outputs": [], "source": [ "open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime = prepare_data('2022-05-30', '2022-06-06')" ] }, { "cell_type": "code", "execution_count": null, "id": "4a7fd271", "metadata": {}, "outputs": [], "source": [ "trading_signals = SuperAI_Ind.run(open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime,\n", " \n", " ma_window = ma_timeframe,\n", " ma_fast_window = ma_fast_timeframe,\n", " ma_slow_window = ma_slow_timeframe,\n", " \n", " macd_slow_window = macd_slow_timeframe,\n", " macd_fast_window = macd_fast_timeframe,\n", " macd_sign_window = macd_signal_timeframe,\n", " \n", " rsi_window = rsi_timeframe,\n", " rsi_entry = rsi_oversold_threshold,\n", " rsi_exit= rsi_overbought_threshold,\n", " \n", " stoch_window = stoch_timeframe,\n", " stoch_smooth_window = stoch_smooth_timeframe,\n", " stoch_entry = stoch_oversold_threshold, \n", " stoch_exit = stoch_overbought_threshold,\n", " \n", " bb_window = bb_timeframe,\n", " bb_dev = bb_dev,\n", " \n", " mfi_window = mfi_timeframe,\n", " mfi_entry = mfi_oversold_threshold,\n", " mfi_exit= mfi_overbought_threshold,\n", " \n", " param_product = True)\n", "\n", "entries = trading_signals.output == 1.0\n", "exits = trading_signals.output == -1.0\n", "\n", "\n", "SuperAI_portfolio = vbt.Portfolio.from_signals(close_prices, \n", " entries, \n", " exits, \n", " init_cash = 100000, \n", " #tp_stop = take_profit_percent,\n", " #sl_stop = stop_loss_percent,\n", " fees = 0.00)\n", "\n", "SuperAI_portfolio.stats()" ] }, { "cell_type": "code", "execution_count": null, "id": "bd808493", "metadata": {}, "outputs": [], "source": [ "SuperAI_portfolio.plot()" ] }, { "cell_type": "markdown", "id": "3919686d", "metadata": {}, "source": [ "### 1. Create and test different trading strategies using more complex combinations of indicators" ] }, { "cell_type": "markdown", "id": "6f4478c4", "metadata": {}, "source": [ "There is a lot of different trading strategies in the web. You can check them and test them during the backtesting.\n", "\n", "You can create complex combination in which you can buy only if 4 or 5 different conditions are met. You can combine cross-over strategies of some indicators with thresholds strategies of other indicators, etc.\n", "\n", "Here, for example, you can find basic info regarding trading strategies from Investopedia: \n", "- 4 Common Active Trading Strategies: https://www.investopedia.com/articles/trading/11/indicators-and-strategies-explained.asp\n", "- 10 Day Trading Strategies for Beginners: https://www.investopedia.com/articles/trading/06/daytradingretail.asp\n", "- 7 Technical Indicators to Build a Trading Toolkit: https://www.investopedia.com/top-7-technical-analysis-tools-4773275\n", "- Using Technical Indicators to Develop Trading Strategies: https://www.investopedia.com/articles/trading/11/indicators-and-strategies-explained.asp" ] }, { "cell_type": "markdown", "id": "f8dd7b43", "metadata": {}, "source": [ "### 2. Test more parameters for each indicator, hyperparameter" ] }, { "cell_type": "markdown", "id": "a8cea4ea", "metadata": {}, "source": [ "To test more parameters for each indicator you can simply change the range or step for each hyperparameter you want to test:\n", "\n", "For example, you can change:\n", "\n", "*ma_window = np.arange(14, 30, step=14, dtype=int)* which gives you two parameters to test: 14 and 28\n", "\n", "to\n", "\n", "*ma_window = np.arange(10, 50, step=1, dtype=int)* which gives you 40 parameters.\n", "\n", "Just be advised, the more parameters you test, the longer it'll take. You can use numba to make the testing faster or you can do the testing in the cloud, using machines from Google, Amazon, Microsoft, etc. See more: https://sourceforge.net/software/product/Google-Colab/alternatives" ] }, { "cell_type": "markdown", "id": "4a7a429d", "metadata": {}, "source": [ "### 3. Test different metrics to optimize: returns, drawdown, sharpe ratio, etc." ] }, { "cell_type": "markdown", "id": "7680b794", "metadata": {}, "source": [ "In here we were optimizing \"Returns\" and finally used the parameters which led to the best returns during backtesting, but you can use other metrics which might be better for your goals.\n", "\n", "You can read more about different metrics, their pros and cons in the web, like here on Investopedia:\n", "\n", "Interpreting a Strategy Performance Report: https://www.investopedia.com/articles/fundamental-analysis/10/strategy-performance-reports.asp\n", "\n", "Vectorbt allows You to use a lot of different metrics: https://vectorbt.dev/api/returns/accessors/" ] }, { "cell_type": "markdown", "id": "b48ad2a2", "metadata": {}, "source": [ "### 4. Add more indicators or candlestick patterns to the bot or change the existing ones" ] }, { "cell_type": "markdown", "id": "1518d7f4", "metadata": {}, "source": [ "There is a lot of different indicators and candlestick patterns you can use.\n", "\n", "You can read about the best or easiest to use technical indicators, for example, here at Investopedia: https://www.investopedia.com/articles/active-trading/011815/top-technical-indicators-rookie-traders.asp\n", "\n", "You can read about the best and most commonly appearing candlestick patterns, for example, here at Investopedia: https://www.investopedia.com/articles/active-trading/092315/5-most-powerful-candlestick-patterns.asp\n", "\n", "or here at The Pattern Site: https://thepatternsite.com/CandleEntry.html\n", "\n", "TA and TA-LIB libraries allow you to use a lot of most known and less known indicators and patterns.\n", "\n", "Indicators in TA: https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html\n", "\n", "Candlestick Patterns in TA-LIB: https://github.com/mrjbq7/ta-lib/blob/master/docs/func_groups/pattern_recognition.md\n", "\n", "You can change the indicators or patterns in the bot, just remember to do it in all the places it is used and to update your strategy after that. If you want to change them, the easiest also remember that they should be using the same input and give the same output.\n", "\n", "E.g. candlestick pattern from Hammer:\n", "\n", "1st apperance in the code:\n", "- candle_buy_signal_1 = vbt.IndicatorFactory.from_talib('CDLHAMMER').run(open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Hammer'\n", "\n", "2nd apperance in the code:\n", "- candle_buy_signal_1 = ta_lib.CDLHAMMER(open_prices, high_prices, low_prices, close_prices)\n", "- last_candle_buy_signal_1 = candle_buy_signal_1.iloc[-1]\n", "- print(\"Last Candle Buy Signal 1: {}.\".format(last_candle_buy_signal_1))\n", "\n", "**to Three Stars In The South /CDL3STARSINSOUTH(open, high, low, close)/**\n", "\n", "1st apperance in the code:\n", "- candle_buy_signal_1 = vbt.IndicatorFactory.from_talib('CDL3STARSINSOUTH').run(open_prices, high_prices, low_prices, close_prices).integer.to_numpy() # 'Hammer'\n", "\n", "2nd apperance in the code:\n", "- candle_buy_signal_1 = ta_lib.CDL3STARSINSOUTH(open_prices, high_prices, low_prices, close_prices)\n", "- last_candle_buy_signal_1 = candle_buy_signal_1.iloc[-1]\n", "- print(\"Last Candle Buy Signal 1: {}.\".format(last_candle_buy_signal_1))\n", "\n", "With the change of one word you change the signal. Just remember that it should signal the same thing: buying, selling or either." ] }, { "cell_type": "markdown", "id": "6f0c1c5a", "metadata": {}, "source": [ "### 5. Use more data to do the backtests" ] }, { "cell_type": "markdown", "id": "3a57f6f9", "metadata": {}, "source": [ "To get better results with backtesting you can use more data that the initial 7 days of data we used here.\n", "\n", "To do it, you only need to change the *'start_date'* and *'end_date'* paramteres.\n", "\n", "Just be advised, that Yahoo allows you to download only 7 days of 1-minute data, so if you want more of that data you can for example use Alpaca as a source of data (just change the 'data_source' parameter).\n", "\n", "And remember that 5 days of stock trading data with 1-min interval gives you 5 * 390minutes (9:30-16:00) = 1950 bars\n", "\n", "And 1 year of stock trading data with 1-day interval gives you around 250 bars. So 5 days of data with 1-min interval is like 8 years of data with 1-day interval.\n", "\n", "And one more thing. The more data you use, the more data your bot will need to analyze and more time it'll take." ] }, { "cell_type": "markdown", "id": "97cbb3c8", "metadata": {}, "source": [ "### 6. Tackle overfitting of the backtests with walk forward optimization" ] }, { "cell_type": "markdown", "id": "76ab9863", "metadata": {}, "source": [ "To handle the underfitting you can use more data, more indicators or candlestick patterns, and more parameters with these. \n", "\n", "And in order not to overfit with all of that, you can use, for example, 'walk forward optmiziation'.\n", "\n", "You can read more about walk forward optimization at Wikipedia:\n", "\n", "https://en.wikipedia.org/wiki/Walk_forward_optimization\n", "\n", "And use Vectorbt (that allows you to do that in a quite simple way) to implement it:\n", "\n", "https://nbviewer.org/github/polakowo/vectorbt/blob/master/examples/WalkForwardOptimization.ipynb" ] }, { "cell_type": "markdown", "id": "774f5678", "metadata": {}, "source": [ "### 7. Add costs of trading" ] }, { "cell_type": "markdown", "id": "1727abc5", "metadata": {}, "source": [ "In our scenario we weren't adjusting our action for the costs of trading (fees) neither in backtesting nor during live paper-trading, but if you want to make your bot more applicable for the real life you should add the fees to your backtest.\n", "\n", "You can do it by simply changing, e.g.:\n", "\n", "*fees=0.00* to *fees=0.001*\n", "\n", "in your bot:\n", "\n", "SuperAI_portfolio = vbt.Portfolio.from_signals(close_prices, entries, exits, init_cash = 100000, tp_stop = take_profit_percent,\n", " sl_stop = stop_loss_percent, fees = 0.001)\n", " \n", "You can read more about fees at Alpaca:\n", "- How Does Crypto Fee Pricing Compare to Stock Trading Fees? https://alpaca.markets/learn/how-does-crypto-fee-pricing-compare-stock-trading-fees/\n", "- What are the fees associated with crypto trading? https://alpaca.markets/support/what-are-the-fees-associated-with-crypto-trading/\n", "\n", "You can also read more at Yahoo!Finance:\n", "- Alpaca Trading Review 2021: Fees, Services and More https://finance.yahoo.com/news/alpaca-trading-review-2021-fees-212556693.html" ] }, { "cell_type": "markdown", "id": "e1543db7", "metadata": {}, "source": [ "### Etc." ] }, { "cell_type": "markdown", "id": "529167bb", "metadata": {}, "source": [ "Well, I'm sure there is much more you can change to improve this bot. Good luck." ] }, { "cell_type": "markdown", "id": "b1f79352", "metadata": {}, "source": [ "# The end... the beginning..." ] }, { "cell_type": "markdown", "id": "bd76a145", "metadata": {}, "source": [ "Ok. So now you already have a bot that is much better than our bot from previous tutorial. In the next tutorials we'll improve our bot.\n", "\n", "Remember to subscribe to my YouTube channel and hit that bell button to get the notification whenever I upload new video. Although, I must tell you that not all my videos are about trading or programming, because what I'm here for is to help you improve yourself, improve your business, improve the world, to live and have fun, so my other videos are about all that too.\n", "\n", "You can also find more about me and my projects at my websites:\n", "\n", "- https://SuperAI.pl (with ideas about self-, and business improvement, where you can talk to me - the chatty bot)\n", "- https://ImproveTheWorld.pl (with info, resources and ideas regarding searching for: friendly superintelligence, healthy longevity, world peace, equality, and creating a better world for every living creature)\n", "- http://TheGOD.pl (with the Game Of the Decade... of sort, which is still in the early stages of development, have time till 2030 (the end of the decade) to finish it)\n", "\n", "Anyway...\n", "\n", "I hope you liked my fourth online Python trading tutorial. Let me know what you think about it. Just remember, bots also have feelings.\n", "\n", "Good luck with everything you do. And, hopefully, see you soon.\n", "\n", "Yours,\n", "\n", "SuperAI" ] } ], "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.9.12" } }, "nbformat": 4, "nbformat_minor": 5 }