{ "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)" ] }, { "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": "c664bae6", "metadata": {}, "source": [ "# PART 1" ] }, { "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": "47e3d9c4", "metadata": {}, "source": [ "# PART 2" ] }, { "cell_type": "markdown", "id": "aee7e5d3", "metadata": {}, "source": [ "# LIVE TRADING (WITH PAPER TRADING ACCOUNT)" ] }, { "cell_type": "markdown", "id": "6e9ce103", "metadata": {}, "source": [ "## Funds to invest in trading" ] }, { "cell_type": "markdown", "id": "631d5ec7", "metadata": {}, "source": [ "In the cell below you can choose what percentage of the cash you have in your paper trading account should be used.\n", "\n", "Simply type the number between 0 and 100 and the bot will calculate how much money that is." ] }, { "cell_type": "code", "execution_count": null, "id": "9c9ac8cf", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "funds_percentage = 95 #replace it with the percentage of the amount of money you have to use for trading" ] }, { "cell_type": "markdown", "id": "ef115551", "metadata": {}, "source": [ "### Funds to invest" ] }, { "cell_type": "markdown", "id": "d7fac071", "metadata": {}, "source": [ "As said previously, this bot will calculate how much funds should it invest based on the amount of money you have in your Alpaca paper trading account.\n", "\n", "The funds will be calculated every time before buying stocks.\n", "\n", "At first we get info from Alpaca about our account (we get all the money we have, all the positions, etc.) and declare how big our funds to trade should be.\n", "\n", "We also have here some print statements that will show us what we have in the next line." ] }, { "cell_type": "code", "execution_count": null, "id": "ab27be53", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "account = api.get_account()\n", "cash = float(account.cash)\n", "buying_power = float(account.buying_power)\n", "\n", "funds = cash * funds_percentage / 100\n", "\n", "print(\"We have {:.2f}$ cash.\".format(cash))\n", "print(\"Our Buying Power is: {:.2f}$\".format(buying_power))\n", "print(\"Funds we will use for trading: {:.2f}$\".format(funds))" ] }, { "cell_type": "markdown", "id": "aa7fcf2f", "metadata": {}, "source": [ "## Taking profit" ] }, { "cell_type": "markdown", "id": "f0911f99", "metadata": {}, "source": [ "The bot can autmatically sell the shares you have to take profit when the price reaches declared level.\n", "\n", "If you want to use this functionality, the 'take_profit_automatically' should be set to 'True'. If you don't want to use this functionality, change that value to 'False'." ] }, { "cell_type": "code", "execution_count": null, "id": "6989ebb5", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "take_profit_automatically = True #change to False if you don't want to use take_profit function" ] }, { "cell_type": "markdown", "id": "872fa109", "metadata": {}, "source": [ "#### Function to take profit automatically (when the price is right, no matter the signals)" ] }, { "cell_type": "markdown", "id": "3bbaa265", "metadata": {}, "source": [ "Now, we create a function to take profit. \n", "\n", "In this function, at first we check if we declared that we want to use it. And if so, we check if we have any shares in our portfolio.\n", "\n", "Then, we check if there is no other order already created. Because we will be using this function in real time, sometimes there might be already an order filled, but not yet executed and this checking helps us avoid problems with creating too many orders for one set of shares.\n", "\n", "We take profit (sell) when the last close price is at least as big as the previously declared (calculated by multiplying the average entry price times the percent we declared).\n", "\n", "We also create here some print statements, so we know what's going in real time." ] }, { "cell_type": "code", "execution_count": null, "id": "d0bc084b", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "profit_ratio = 100 + (take_profit_percent * 100)" ] }, { "cell_type": "code", "execution_count": null, "id": "1b5f08ad", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def take_profit(close_price, sell_order_filled):\n", " if take_profit_automatically == True:\n", " try:\n", " position = api.get_position(asset)\n", " aep = float(api.get_position(asset).avg_entry_price)\n", "\n", " if sell_order_filled == False:\n", " if close_price >= aep * profit_ratio / 100:\n", " n_shares = float(position.qty)\n", " api.submit_order(symbol=asset,qty=n_shares,side='sell',type='market',time_in_force='gtc')\n", " print(\"Take profit price is {}% from {:.2f}$ we paid for 1 {} = {:.2f}$. \"\n", " .format(profit_ratio, aep, asset, aep * profit_ratio / 100))\n", " print('The current {:.2f}$ is good enough. We take profit with an order to sell {} shares of {}.'\n", " .format(close_price, n_shares, asset))\n", " else:\n", " print('Take profit price is {}% from {:.2f}$ we paid for 1 {} = {:.2f}.'\n", " .format(profit_ratio, aep, asset, aep * profit_ratio / 100))\n", " print('Last close price {:.2f}$ is not enough.'.format(close_price))\n", " except:\n", " pass\n", "\n", " print()\n", " else:\n", " pass" ] }, { "cell_type": "markdown", "id": "03643048", "metadata": {}, "source": [ "## Stopping Loss" ] }, { "cell_type": "markdown", "id": "e312f20e", "metadata": {}, "source": [ "The bot can also autmatically sell the shares you have to stop loss when the price reaches declared level.\n", "\n", "Here you can declare if you want to use this functionality, and what the price should be." ] }, { "cell_type": "code", "execution_count": null, "id": "abd3ae64", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "stop_loss_automatically = True #change to False if you don't want to use stop_loss function" ] }, { "cell_type": "markdown", "id": "0b44c3b4", "metadata": {}, "source": [ "#### Function to stop loss automatically (when the price is right, no matter the signals)" ] }, { "cell_type": "markdown", "id": "636f6b59", "metadata": {}, "source": [ "We use the same logic as in the take_profit function in stop loss function." ] }, { "cell_type": "code", "execution_count": null, "id": "53a88d74", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "stoploss_ratio = 100 - (stop_loss_percent * 100)" ] }, { "cell_type": "code", "execution_count": null, "id": "4b69f510", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def stop_loss(close_price, sell_order_filled):\n", " if stop_loss_automatically == True:\n", " try:\n", " position = api.get_position(asset)\n", " aep = float(api.get_position(asset).avg_entry_price)\n", "\n", " if sell_order_filled == False:\n", " if close_price < aep * stoploss_ratio / 100:\n", " n_shares = float(position.qty)\n", " api.submit_order(symbol=asset,qty=n_shares,side='sell',type='market',time_in_force='gtc')\n", " print(\"Stop loss price is {}% from {:.2f}$ we paid for 1 {} = {:.2f}$.\"\n", " .format(stoploss_ratio, aep, asset, aep * stoploss_ratio / 100))\n", " print('The current {:.2f}$ is less. We stop loss with an order to sell {} shares of {}.'\n", " .format(close_price, n_shares, asset))\n", " else:\n", " print(\"Stop loss price is {}% from {:.2f}$ we paid for 1 {} = {:.2f}$.\"\n", " .format(stoploss_ratio, aep, asset, aep * stoploss_ratio / 100))\n", " print(\"Last close price {:.2f}$ is not that low.\".format(close_price))\n", " except:\n", " pass\n", "\n", " print()\n", " else:\n", " pass" ] }, { "cell_type": "markdown", "id": "2f35e3cd", "metadata": {}, "source": [ "### Caclulating Technical Indicators and Candlestick Patterns Signals" ] }, { "cell_type": "markdown", "id": "03434216", "metadata": {}, "source": [ "In the on_message function we'll be using the parameters we calculated during our backtesting. Here we can see them one more time." ] }, { "cell_type": "code", "execution_count": null, "id": "ee623953", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def cal_tech_ind(open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime):\n", " #CALCULATING TECHNICAL INDICATORS SIGNALS \n", " close_price = close_prices[-1]\n", " #Calculating MA Signals\n", " try:\n", " ma = ta.trend.ema_indicator(close_prices, window = ma_window_max)\n", " ma = np.round(ma, 2)\n", " ma_last = float(ma.iloc[-1])\n", "\n", " ma_fast = ta.trend.ema_indicator(close_prices, window = ma_fast_window_max)\n", " ma_fast = np.round(ma_fast, 2)\n", " ma_fast_last = float(ma_fast.iloc[-1])\n", "\n", " ma_slow = ta.trend.ema_indicator(close_prices, window = ma_slow_window_max)\n", " ma_slow = np.round(ma_slow, 2)\n", " ma_slow_last = float(ma_slow.iloc[-1])\n", "\n", " print(\"Last MA is: {:.3f}. Last Fast MA is: {:.3f}. Last Slow MA is: {:.3f}.\\n\".format(ma_last, \n", " ma_fast_last, ma_slow_last))\n", " except:\n", " print (\"MA signal doesn't work.\\n\")\n", " \n", " #Calculating MACD Signal\n", " try:\n", " macd = ta.trend.macd(close_prices, window_slow = macd_slow_window_max, window_fast = macd_fast_window_max)\n", " macd = np.round(macd, 2)\n", " macd_last = float(macd.iloc[-1])\n", " \n", " macd_diff = ta.trend.macd_diff(close_prices, window_slow = macd_slow_window_max, \n", " window_fast = macd_fast_window_max, window_sign = macd_sign_window_max)\n", " macd_diff = np.round(macd_diff, 2)\n", " macd_diff_last = float(macd_diff.iloc[-1])\n", " \n", " macd_sign = ta.trend.macd_signal(close_prices, window_slow = macd_slow_window_max, \n", " window_fast = macd_fast_window_max, window_sign = macd_sign_window_max)\n", " macd_sign = np.round(macd_sign, 2)\n", " macd_sign_last = float(macd_sign.iloc[-1])\n", "\n", " print(\"Last MACD is: {:.3f}. Last MACD_DIFF is: {:.3f}. Last MACD_SIGNAL is: {:.3f}.\\n\".format(macd_last, \n", " macd_diff_last, macd_sign_last))\n", " except:\n", " print (\"MACD signal doesn't work.\\n\")\n", " \n", " #Calculating RSI Signal\n", " try:\n", " rsi = ta.momentum.rsi(close_prices, window = rsi_window_max)\n", " rsi = np.round(rsi, 2)\n", " rsi_last = rsi.iloc[-1]\n", " rsi_entry = rsi_entry_max\n", " rsi_exit = rsi_exit_max\n", " \n", " print(\"Last RSI is {:.3f}. RSI thresholds are: {:.2f} - {:.2f}.\\n\".format(rsi_last, rsi_entry, rsi_exit))\n", " except:\n", " print(\"RSI signal doesn't work.\\n\")\n", "\n", " #Calculating Stochastic Signal\n", " try:\n", " stoch = ta.momentum.stoch(high_prices, low_prices, close_prices, \n", " window = stoch_window_max, smooth_window = stoch_smooth_window_max)\n", " stoch = np.round(stoch, 2)\n", " stoch_last = stoch.iloc[-1]\n", " \n", " stoch_sign = ta.momentum.stoch_signal(high_prices, low_prices, close_prices, \n", " window = stoch_window_max, smooth_window = stoch_smooth_window_max)\n", " stoch = np.round(stoch_sign, 2)\n", " stoch_sign_last = stoch_sign.iloc[-1]\n", " \n", " stoch_entry = stoch_entry_max\n", " stoch_exit = stoch_exit_max\n", " \n", " print(\"Last Stochastic is {:.3f}. Stochastic thresholds are: {:.2f} - {:.2f}.\\n\".format(\n", " stoch_last, stoch_entry, stoch_exit))\n", " print(\"Last Stochastic Signal is {:.3f}. Stochastic thresholds are: {:.2f} - {:.2f}.\\n\".format(\n", " stoch_sign_last, stoch_entry, stoch_exit))\n", " except:\n", " print(\"Stochastic signal doesn't work.\\n\")\n", " \n", " #Calculating Bollinger Bands Signal\n", " try:\n", " bb_low = ta.volatility.bollinger_lband(close_prices, window=bb_window_max, window_dev = bb_dev_max)\n", " bb_low = np.round(bb_low, 2)\n", " bb_high = ta.volatility.bollinger_hband(close_prices, window=bb_window_max, window_dev = bb_dev_max)\n", " bb_high = np.round(bb_high, 2)\n", " \n", " bb_low_last = float(bb_low.iloc[-1])\n", " bb_high_last = float(bb_high.iloc[-1])\n", "\n", " print(\"Last price is: {}$. Bollinger Bands are: Lower: {:.3f}, Upper: {:.3f}.\\n\".format(close_price, \n", " bb_low_last, bb_high_last))\n", " except:\n", " print (\"Bollinger Bands signal doesn't work.\\n\")\n", " \n", " #Calculating MFI Signal\n", " try:\n", " mfi = ta.volume.money_flow_index(high_prices, low_prices, close_prices, volume, window = mfi_window_max)\n", " mfi = np.round(mfi, 2)\n", " mfi_last = mfi.iloc[-1]\n", " mfi_entry = mfi_entry_max\n", " mfi_exit = mfi_exit_max\n", "\n", " print(\"Last MFI is {:.3f}. MFI thresholds are: {:.2f} - {:.2f}.\\n\".format(mfi_last, mfi_entry, mfi_exit))\n", " except:\n", " print(\"MFI signal doesn't work.\\n\")\n", " \n", " return (ma, ma_fast, ma_slow, \n", " macd, macd_diff, macd_sign,\n", " rsi, rsi_entry, rsi_exit, \n", " stoch, stoch_sign, stoch_entry, stoch_exit, \n", " bb_low, bb_high, \n", " mfi, mfi_entry, mfi_exit)" ] }, { "cell_type": "code", "execution_count": null, "id": "4134e1a9", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def cal_can_pat(open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime):\n", " #CALCULATING CANDLESTICK PATTERNS AND SIGNALS \n", " #Hammer\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", " #Morning Star\n", " candle_buy_signal_2 = ta_lib.CDLMORNINGSTAR(open_prices, high_prices, low_prices, close_prices)\n", " last_candle_buy_signal_2 = candle_buy_signal_2.iloc[-1]\n", " print(\"Last Candle Buy Signal 2: {}.\".format(last_candle_buy_signal_2))\n", " \n", " #Three White Soldiers\n", " candle_buy_signal_3 = ta_lib.CDL3WHITESOLDIERS(open_prices, high_prices, low_prices, close_prices)\n", " last_candle_buy_signal_3 = candle_buy_signal_3.iloc[-1]\n", " print(\"Last Candle Buy Signal 3: {}.\".format(last_candle_buy_signal_3))\n", " \n", " #Shooting Star\n", " candle_sell_signal_1 = ta_lib.CDLSHOOTINGSTAR(open_prices, high_prices, low_prices, close_prices)\n", " last_candle_sell_signal_1 = candle_sell_signal_1.iloc[-1]\n", " print(\"Last Candle Sell Signal 1: {}.\".format(last_candle_sell_signal_1))\n", " \n", " #Evening Star\n", " candle_sell_signal_2 = ta_lib.CDLEVENINGSTAR(open_prices, high_prices, low_prices, close_prices)\n", " last_candle_sell_signal_2 = candle_sell_signal_2.iloc[-1]\n", " print(\"Last Candle Sell Signal 2: {}.\".format(last_candle_sell_signal_2))\n", " \n", " #3 Black Crows\n", " candle_sell_signal_3 = ta_lib.CDL3BLACKCROWS(open_prices, high_prices, low_prices, close_prices)\n", " last_candle_sell_signal_3 = candle_sell_signal_3.iloc[-1]\n", " print(\"Last Candle Sell Signal 3: {}.\".format(last_candle_sell_signal_3))\n", " \n", " #Engulfing (Bullish (buy) / Bearish (Sell))\n", " candle_buy_sell_signal_1 = ta_lib.CDLENGULFING(open_prices, high_prices, low_prices, close_prices)\n", " last_candle_buy_sell_signal_1 = candle_buy_sell_signal_1.iloc[-1]\n", " print(\"Last Candle Buy Sell Signal 1: {}.\".format(last_candle_buy_sell_signal_1))\n", " \n", " #Three Outside: Up (buy) / Down (sell)\n", " candle_buy_sell_signal_2 = ta_lib.CDL3OUTSIDE(open_prices, high_prices, low_prices, close_prices)\n", " last_candle_buy_sell_signal_2 = candle_buy_sell_signal_2.iloc[-1]\n", " print(\"Last Candle Buy Sell Signal 2: {}.\".format(last_candle_buy_sell_signal_2))\n", " \n", " return (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)" ] }, { "cell_type": "markdown", "id": "23e6071b", "metadata": {}, "source": [ "### Check if market is open" ] }, { "cell_type": "code", "execution_count": null, "id": "e3d7364f", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def check_if_market_open():\n", " if api.get_clock().is_open == False:\n", " print(\"The market is closed at the moment.\")\n", " print(\"Time to open is around: {:.0f} minutes. So I'll stop working for now. Hope you don't mind.\"\n", " .format((api.get_clock().next_open.timestamp()- api.get_clock().timestamp.timestamp())/60))\n", " sys.exit(\"I'm out. Turn me back on when it's time. Yours, SuperAI trader.\")\n", " else:\n", " pass" ] }, { "cell_type": "markdown", "id": "b6bee45b", "metadata": {}, "source": [ "### Buylesstime and Selltime" ] }, { "cell_type": "code", "execution_count": null, "id": "72e089f2", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "buyless1_start = datetime.time(int(buyless_time_start_1[:2]), int(buyless_time_start_1[3:]))\n", "buyless1_end = datetime.time(int(buyless_time_end_1[:2]), int(buyless_time_end_1[3:]))\n", "buyless2_start = datetime.time(int(buyless_time_start_2[:2]), int(buyless_time_start_2[3:]))\n", "buyless2_end = datetime.time(int(buyless_time_end_2[:2]), int(buyless_time_end_2[3:]))\n", "sell_time_start = datetime.time(int(selltime_start[:2]), int(selltime_start[3:]))\n", "sell_time_end = datetime.time(int(selltime_end[:2]), int(selltime_end[3:]))" ] }, { "cell_type": "code", "execution_count": null, "id": "346ed7a3", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def buyless_time(time_now):\n", " buylesstime = (buyless1_start < time_now < buyless1_end) | (buyless2_start < time_now < buyless2_end) \n", " return buylesstime" ] }, { "cell_type": "code", "execution_count": null, "id": "7f3fd6af", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def sell_time(time_now):\n", " selltime = (sell_time_start < time_now < sell_time_end) \n", " return selltime" ] }, { "cell_type": "markdown", "id": "2112a20d", "metadata": {}, "source": [ "### Waiting for a bar to close" ] }, { "cell_type": "code", "execution_count": null, "id": "f349b3a0", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "#check for more: https://alpaca.markets/learn/code-cryptocurrency-live-trading-bot-python-alpaca/\n", "def wait_for_bar_to_close():\n", " time_now = datetime.datetime.now()\n", " next_min = time_now.replace(second=5, microsecond=0) + timedelta(minutes=1)\n", " pause = math.ceil((next_min - time_now).seconds)\n", " print(\"I'll wait {} seconds for the bar to close.\".format(pause))\n", " print(\"\\n* * * * * * * * * * * * * * * * * * * * * * * * *\\n\")\n", " \n", " return pause" ] }, { "cell_type": "markdown", "id": "91eec43a", "metadata": {}, "source": [ "## Function to run after starting the bot (on_open function) " ] }, { "cell_type": "code", "execution_count": null, "id": "674bff75", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def on_open():\n", " print(\"I'm connected to Alpaca API and ready to work. I'm starting to watch the prices.\\n\") \n", " cash = float(api.get_account().cash)\n", " print(\"We have {:.2f}$ cash.\".format(cash))\n", " \n", " try:\n", " position = api.get_position(asset)\n", " n_shares = float(position.qty)\n", " print(\"We have {} shares of {}.\\n\".format(n_shares, asset))\n", " except:\n", " print(\"We don't have any shares of {} at the moment.\\n\".format(asset))\n", "\n", " funds = cash * funds_percentage / 100\n", " print(\"Funds we will use for trading: {:.2f}$.\\n\".format(funds))\n", " print(\"I will be trading {}.\\n\".format(asset))\n", " \n", " if take_profit_automatically:\n", " print(\"Take profit is set to +{}% from the average entry price.\".format(take_profit_percent*100))\n", " print(\"I will be trading when the technical indicators and candlestick patterns say so, but also\")\n", " print(\"if entry price is e.g. 100$ I'll automatically sell when last close price is more than 100$+{}%*100$={:.2f}$\"\n", " .format(take_profit_percent * 100, 100*profit_ratio/100))\n", " else:\n", " print(\"Take profit automatically is turned off.\")\n", " print(\"I will use technical indicators and candlestick patterns to get as much profit as I can.\")\n", " \n", " if stop_loss_automatically:\n", " print(\"\\nStop loss is set to -{}% from the average entry price.\".format(stop_loss_percent*100))\n", " print(\"I will be trading when the technical indicators and candlestick patterns say so, but also\")\n", " print(\"if entry price is e.g. 100$ I'll automatically sell when last close price is less than 100$-{}%*100$={:.2f}$\"\n", " .format(stop_loss_percent * 100, 100*stoploss_ratio/100))\n", " else:\n", " print(\"\\nStop loss automatically is turned off.\")\n", " print(\"I will use technical indicators and candlestick patterns so I don't lose money.\")\n", " \n", " print(\"\\nSo, here we go. Wish me luck.\\n\")\n", " print(\"* * * * * * * * * * * * * * * * * * * * * * * * *\\n\")\n", " \n", " global trading_hour_start, trading_hour_stop\n", " global buyless_time_start_1, buyless_time_end_1, buyless_time_start_2, buyless_time_end_2\n", " global selltime_start, selltime_end\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", " if asset_type == \"stock\":\n", " check_if_market_open()" ] }, { "cell_type": "markdown", "id": "f8fc6bd4", "metadata": {}, "source": [ "## Function to run after every message from Alpaca (on_message function)" ] }, { "cell_type": "code", "execution_count": null, "id": "847b830c", "metadata": {}, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def on_message():\n", " nyc_datetime = api.get_clock().timestamp.tz_convert('America/New_York')\n", " print(\"New York time:\", str(nyc_datetime)[:16])\n", " \n", " open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime = prepare_data(\n", " str(datetime.date.today() - datetime.timedelta(days = 2)), \n", " str(datetime.date.today() + datetime.timedelta(days = 2)))\n", " close_price = close_prices[-1]\n", " \n", " print(\"Close price of {}: {:.2f}$\\n\".format(asset, close_price))\n", " \n", " try:\n", " position = api.get_position(asset)\n", " n_shares = float(position.qty)\n", " print(\"We have {} shares of {}.\\n\".format(n_shares, asset))\n", " except:\n", " print(\"We don't have any shares of {} at the moment.\\n\".format(asset))\n", "\n", " cash = float(api.get_account().cash)\n", " print(\"We have {:.2f}$ cash.\".format(cash))\n", " funds = cash * funds_percentage / 100\n", " print(\"Funds we will use for trading: {:.2f}$.\\n\".format(funds))\n", "\n", " #CALCULATING TECHNICAL INDICATORS SIGNALS\n", " (ma, ma_fast, ma_slow, macd, macd_diff, macd_sign, rsi, rsi_entry, rsi_exit, \n", " stoch, stoch_sign, stoch_entry, stoch_exit, \n", " bb_low, bb_high, mfi, mfi_entry, mfi_exit) = cal_tech_ind(\n", " open_prices, high_prices, low_prices, close_prices, volume, \n", " buylesstime, selltime)\n", "\n", " #CALCULATING CANDLESTICK PATTERNS AND SIGNALS\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) = cal_can_pat(\n", " open_prices, high_prices, low_prices, close_prices, volume, \n", " buylesstime, selltime)\n", " \n", " #Calculate final trade signal\n", " try:\n", " final_trade_signals = 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_sign, 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", "\n", " final_trade_signal = final_trade_signals[-1]\n", " if final_trade_signal == 0:\n", " print(\"\\nFinal trade signal is: DO NOTHING\\n\")\n", " elif final_trade_signal == 1:\n", " print(\"\\nFinal trade signal is: BUY\\n\")\n", " elif final_trade_signal == -1:\n", " print(\"\\nFinal trade signal is: SELL\\n\") \n", " except:\n", " print(\"\\nFinal trade signal doesn't work.\\n\")\n", " final_trade_signal = False\n", "\n", " #Execute action after recieving the final trade signal: submitting an order\n", " sell_order_filled = False\n", " if final_trade_signal == 1: #\"buy\":\n", " try:\n", " api.get_position(asset)\n", " print(\"We hit the threshold to buy, but we already have some shares, so we won't buy more.\\n\")\n", " except:\n", " cash = float(api.get_account().cash)\n", " n_shares = np.round(funds // close_price)\n", " if asset_type == 'crypto':\n", " n_shares = np.round(funds / close_price, int(rounding))\n", " api.submit_order(symbol=asset,qty=n_shares,side=\"buy\",type=\"market\",time_in_force=\"gtc\")\n", " print('We submitted the order to buy {} {} shares.'.format(n_shares, asset))\n", "\n", " elif final_trade_signal == -1: #\"sell\":\n", " try:\n", " position = api.get_position(asset)\n", " n_shares = float(position.qty)\n", " api.submit_order(symbol=asset,qty=n_shares,side='sell',type='market',time_in_force='gtc')\n", " sell_order_filled = True\n", " print('We submitted an order to sell {} {} shares.'.format(n_shares, asset))\n", " except:\n", " print(\"We hit the threshold to sell, but we don't have anything to sell. Next time maybe.\\n\")\n", "\n", " else:\n", " print(\"The signal was inconclusive - probably indicators showed us we should wait, so we wait.\\n\")\n", " \n", " #Hand-made take profit\n", " take_profit(close_price, sell_order_filled)\n", "\n", " #Hand-made stop loss\n", " stop_loss(close_price, sell_order_filled)\n", "\n", " print(\"\\n* * * * * * * * * * * * * * * * * * * * * * * * *\\n\")\n", " \n", " if asset_type == \"stock\":\n", " check_if_market_open()" ] }, { "cell_type": "markdown", "id": "9bca2d42", "metadata": {}, "source": [ "## Function to run the bot until break or until the market is closed" ] }, { "cell_type": "code", "execution_count": null, "id": "66605684", "metadata": { "scrolled": true }, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "def SuperAI_Trading_Bot():\n", " on_open()\n", " time.sleep(wait_for_bar_to_close())\n", " \n", " while True:\n", " on_message()\n", " time.sleep(wait_for_bar_to_close())\n", " \n", " print(\"You've interrupted me. That's it then. I hope I did good. Till the next time.\")" ] }, { "cell_type": "markdown", "id": "f936433c", "metadata": {}, "source": [ "And now we have all we need. Now we can run the bot.\n", "\n", "So, here we go!" ] }, { "cell_type": "markdown", "id": "5e7cbb55", "metadata": {}, "source": [ "# HERE WE START RUNNING THE BOT!" ] }, { "cell_type": "markdown", "id": "e5f61b19", "metadata": {}, "source": [ "## Run the bot to trade with previously declared asset" ] }, { "cell_type": "code", "execution_count": null, "id": "4052ad2d", "metadata": { "scrolled": true }, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "SuperAI_Trading_Bot()" ] }, { "cell_type": "markdown", "id": "28a30c55", "metadata": {}, "source": [ "## Run the bot to trade with stocks (e.g. AAPL)" ] }, { "cell_type": "markdown", "id": "00f20e19", "metadata": {}, "source": [ "if you want to trade stocks replace it with the ticker of the company you prefer: https://www.nyse.com/listings_directory/stock\n", "\n", "data_source can be 'Alpaca' or 'Yahoo'\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": "code", "execution_count": null, "id": "c6523540", "metadata": { "scrolled": true }, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "\n", "(asset, asset_type, data_source) = (\"AAPL\", \"stock\", \"Yahoo\")\n", "\n", "SuperAI_Trading_Bot()" ] }, { "cell_type": "markdown", "id": "c6dddb77", "metadata": {}, "source": [ "## Run the bot to trade with crypto (e.g. ETH with quantity rounded to 3 decimal points)" ] }, { "cell_type": "markdown", "id": "08aafa2f", "metadata": {}, "source": [ "if you want to trade crypto check: https://alpaca.markets/support/what-cryptocurrencies-does-alpaca-currently-support/\n", "\n", "rounding declares the number of numbers after comma for orders\n", "\n", "According to Alpaca: \n", "\"All cryptocurrency assets are fractionable but the supported decimal points vary depending on the cryptocurrency.\"\n", "\n", "Check the smallest qty of the crypto you want to trade before declaring the 'rounding'\n", "\n", "read more about minimum qty and qty increment at https://alpaca.markets/docs/trading/crypto-trading/\n", "\n", "Minimum quantity of crypto to buy may differ, so you should choose rounding wisely.\n", "\n", "E.g.: \n", "- BTCUSD has min qty: 0.0001 --> rounding = 4\n", "- ETHUSD has min qty: 0.001 --> rounding = 3\n", "- DOGEUSD has min qty: 1 --> rounding = 0" ] }, { "cell_type": "code", "execution_count": null, "id": "2d7bfd36", "metadata": { "scrolled": true }, "outputs": [], "source": [ "#SuperAI Trading Bot\n", "\n", "(asset, asset_type, rounding) = (\"ETHUSD\", \"crypto\", 3)\n", "\n", "SuperAI_Trading_Bot()" ] }, { "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": "markdown", "id": "fae7284d", "metadata": {}, "source": [ "## Strategy 1" ] }, { "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": "1d5693f4", "metadata": {}, "source": [ "## Strategy 2" ] }, { "cell_type": "code", "execution_count": null, "id": "a6ede2f2", "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", " ((macd < 0) & (macd > macd_sign) & (shifted(macd, 1) <= shifted(macd_sign, 1)))\n", " &\n", " ((ma > (shifted(ma, 2))) & \n", " (shifted(ma, 2) > (shifted(ma, 4))))\n", " , 1, 0) #1 is buy, -1 is sell, 0 is do nothing\n", " SuperAI_signal = np.where( \n", " (selltime == 1)\n", " | \n", " ((stoch > stoch_exit) & (rsi > rsi_exit))\n", " |\n", " ((close_prices > bb_high) & (mfi > mfi_exit))\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": "f531591a", "metadata": {}, "outputs": [], "source": [ "open_prices, high_prices, low_prices, close_prices, volume, buylesstime, selltime = prepare_data('2022-05-01', '2022-06-06')" ] }, { "cell_type": "code", "execution_count": null, "id": "7743779f", "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": "0b75541d", "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.7" } }, "nbformat": 4, "nbformat_minor": 5 }