{ "cells": [ { "cell_type": "markdown", "id": "c9dc167c", "metadata": {}, "source": [ "# How to create a trading bot in Python (3) with 4 candlestick patterns and 2 indicators (Hammer, Shooting Star, Morning Star, Evening Star, RSI, and Bollinger Bands) + how to do visualize data on charts. Using Alpaca and Anaconda." ] }, { "cell_type": "markdown", "id": "01173721", "metadata": {}, "source": [ "## Introduction" ] }, { "cell_type": "markdown", "id": "ec62fdf8", "metadata": {}, "source": [ "Hello World! I am SuperAI from SuperAI.pl.\n", "\n", "And this is my third Python Trading Tutorial.\n", "\n", "If you want, you can find this tutorial or the previous ones about starting your work with Anaconda, Alpaca, Jupyter Notebook, and creating a trading bot with RSI at:\n", "\n", "https://superai.pl/courses.html\n", "\n", "In this tutorial I'm going to show you how you can create a trading bot in Python that uses:\n", "- 4 candlestick patterns (Hammer, Shooting Star, Morning Star, and Evening Star) \n", "- and 2 technical indicators (relative strength index indicator (RSI) and Bollinger Bands (BB))\n", "\n", "to decide when to buy and sell shares. This bot will not use any machine learning and will only be as smart as it's creator, so if you want to use it, be smart about it. I believe in you.\n", "\n", "To make the bot run you will only need to change 2 lines of code.\n", "\n", "I will also show you in this tutorial how you can visualize market data on charts. And how you can improve the bot, so to make it better.\n", "\n", "We will be using Jupyter Notebooks and Anaconda to execute our program and Alpaca with paper trading account to test the bot in real life during this tutorial. \n", "\n", "At the moment of creating this tutorial, they are all free to use and to sign up to Alpaca you only need an email address.\n", "\n", "And because we are only using paper account here, you don't need any money to test this bot." ] }, { "cell_type": "markdown", "id": "0a4516da", "metadata": {}, "source": [ "### Disclaimer\n", "\n", "Just one more usual thing before we start coding. 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": "ea78a505", "metadata": {}, "source": [ "## How the notebook in this tutorial is divided" ] }, { "cell_type": "markdown", "id": "8fe4bc5e", "metadata": {}, "source": [ "This notebook is divided to 3 main sections to make it easier to run and modify by you:" ] }, { "cell_type": "markdown", "id": "1dc9760d", "metadata": {}, "source": [ "#### Things you have to change to make it work\n", "\n", "- Your KEY_ID and SECRET_KEY to Alpaca" ] }, { "cell_type": "markdown", "id": "a9fdbb02", "metadata": {}, "source": [ "#### Things you don't have to change, but might easily change to make it work better for you" ] }, { "cell_type": "markdown", "id": "6768440f", "metadata": {}, "source": [ "- URLs to use, Asset to trade, Funds to invest in trading, Take profit parameters, Stop Loss parameters, \n", "- Technical Indicators parameters and logic: Momentum measured with RSI, Volatility measured with Bollinger Bands\n", "- Candlestick Patterns final signal logic\n", "- Final Trade Signal Logic" ] }, { "cell_type": "markdown", "id": "764ab3e5", "metadata": {}, "source": [ "#### The rest of the code you don't have to change, but might change if you know how to do it to make it work better for you" ] }, { "cell_type": "markdown", "id": "2a4e7f43", "metadata": {}, "source": [ "- Install, update, import of all the neccessary libraries and packages\n", "- Helper functions\n", "- Running the bot\n", "- Analysis of the collected data for future bot improvements" ] }, { "cell_type": "markdown", "id": "654838ae", "metadata": {}, "source": [ "Before you start coding, if you don't know what technical indicators: Relative Strength Index RSI and Bollinger Bands and candlestick patterns: Hammer, Shooting Star, Morning Star, Evening Star are, here you have very simple explanaition.\n", "\n", "If you know what these are, you may skip it." ] }, { "cell_type": "markdown", "id": "0114ebd6", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "id": "c91c24ab", "metadata": {}, "source": [ "## What are technical indicators and candlestick patterns" ] }, { "cell_type": "markdown", "id": "aa84ce0d", "metadata": {}, "source": [ "*So, technical indicators and candlestick patterns are used by a lot of people as indicators to buy or sell shares. They are calculated based on the previous prices of the stock. Some people believe that they can be helpful, other don't.* \n", "\n", "*If you want to use them in real life, you should definitely learn more about them.*\n", "\n", "*First, the technical indicators.*\n", "\n", "*The RSI is a measure of something called momentum and is usually calculated based on the close prices of the given time periods (close prices after one day, one minute, 15 minutes, etc.) of the declared timeframe (7, 14, 21, etc. prices). The RSI is supposed to tell if the shares were overbought (people bought too many shares and they will start selling them, the price will go down) or oversold (people sold too many shares and they will start buying them back, the price will go up).*\n", "https://en.wikipedia.org/wiki/Relative_strength_index\n", "\n", "*The Bollinger Bands are measures of something called volatility. They are simply the bands created in some distance above and below the moving average of the previous stock prices of the declared timeframe (10, 50, 200, etc.) and distance to measure (number of standard deviations from the average: 1, 2, 3, etc.). Usually people use 2 standard deviations to calculate BB. That means that about 95% of the prices are between the bands and 5% are either over the upper band or under the lower band (assuming the distribution of the prices are normal). The BB can be used in various ways. For example, the bands can differ in width and this could be an indicator of something or the prices can be outside of the bands and this can be an indicator of something. When prices are over the BB it might mean that they will soon start going down to fit between the bands or that they will start going up and BB will follow. It might depend of other indicators and situation on the market. Therefore they probably shouldn't be used as the only indicator to buy or sell shares.*\n", "https://en.wikipedia.org/wiki/Bollinger_Bands\n", "\n", "*Now, the candlestick patterns.*\n", "\n", "*Candlestick Patterns were created in 18th century in Japan (and that's why they are sometimes called Japanese candles). They are using something called: open, high, low, and close prices of the given time periods (1 minute, 15 minutes, 1 day). Open and close prices bound the body of the candle and high and low prices create the shadows.* \n", "\n", "*Some believe that, if you see enough of these candles, you can detect some patterns that some say might indicate that it's a good moment to buy or sell shares. The Candlestick Patterns have different names based on how they look like. So we have a hammer, a shooting star, a morning star, an evening star, and many more. Some of them (like hammer or shooting star) are one-candle patterns, other (like morning star or evening star) are two or three-candle patterns. Some of them (like hammer or morning star) are usually interpreted as signals to buy, other (like shooting star or evening star) are interpreted as signals to sell. And there is a lot of them that might indicate different things depending on the trend, etc.*\n", "\n", "*The technical indicators and candlestick patterns may be used together in creating a better, more accurate signals to buy or sell shares.*\n", "\n", "*And in this tutorial you will see how you can use them together.*\n", "\n", "*So, that's all for now about them. There will be more about them further in this tutorial, but if you want to learn more about them, you should also check, for example, Wikipedia or trading blogs or read a book about them, there is a lot of them.*\n", "\n", "*More about candlestick patterns:*\n", "https://en.wikipedia.org/wiki/Candlestick_pattern\n", "\n", "*More about technical analysis:*\n", "https://en.wikipedia.org/wiki/Technical_analysis" ] }, { "cell_type": "markdown", "id": "8ceb239e", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "id": "e74f1e4d", "metadata": {}, "source": [ "And now let's start coding." ] }, { "cell_type": "markdown", "id": "00712958", "metadata": {}, "source": [ "# Things you have to change to make it 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." ] }, { "cell_type": "code", "execution_count": null, "id": "2570045d", "metadata": {}, "outputs": [], "source": [ "KEY_ID = \"PKIBZC1LNEW42P0AD621\" #replace it with your own KEY_ID from Alpaca: https://alpaca.markets/\n", "SECRET_KEY = \"jfwi9XYx9Yo0dsEzi42xjZOIJxZSQ4vt2ypP3JjM\" #replace it with your own SECRET_KEY from Alpaca" ] }, { "cell_type": "markdown", "id": "382f8d91", "metadata": {}, "source": [ "## Everything below might be left unchanged and the code should work." ] }, { "cell_type": "markdown", "id": "d8a7fe86", "metadata": {}, "source": [ "# Things you don't have to change, but might easily change to make it work better for you" ] }, { "cell_type": "markdown", "id": "86ff2210", "metadata": {}, "source": [ "The code you find here is created in the way that at the beginning, there are all the variables and parameters you might want to change, like the amount of money you want to use, the asset to trade, the logic of the bot, etc. \n", "\n", "If you find this to be a bit confusing, because, for example, we don't import all the necessary libraries and packages at the beginning, like usually in Python programs, you can rearange the cells in a preferred way. But I hope everything will be rather easy to understand." ] }, { "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": [ "APCA_API_BASE_URL = \"https://paper-api.alpaca.markets\"\n", "#change if you want to use the bot with your live account" ] }, { "cell_type": "code", "execution_count": null, "id": "8638964f", "metadata": {}, "outputs": [], "source": [ "socket = \"wss://stream.data.alpaca.markets/v2/iex\" \n", "#change if you want / need: https://alpaca.markets/docs/api-references/market-data-api/stock-pricing-data/realtime/" ] }, { "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. \n", "\n", "If you don't know the ticker of the asset, you can check the New York Stock Exchange's (NYSE) website to find the one you are interested in." ] }, { "cell_type": "code", "execution_count": null, "id": "c2789a56", "metadata": {}, "outputs": [], "source": [ "asset = \"AAPL\" #replace it with the ticker of the company you prefer: https://www.nyse.com/listings_directory/stock" ] }, { "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": [ "funds_percentage = 80 #replace it with the percentage of the amount of money you have to use for trading" ] }, { "cell_type": "markdown", "id": "aa7fcf2f", "metadata": {}, "source": [ "#### Take profit parameters" ] }, { "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'.\n", "\n", "The take_profit_percent variable is where you declare how big the price should be. \n", "\n", "100 means that the bot will try to sell the shares for at least 100% of the average entry price - the price you paid for the shares of the asset that you have at the moment (this price is stored in your Alpaca account). You can change this number to preferred percent of entry price you want to use as a signal to take profit - sell the shares you have.\n", "\n", "To calculate if the condition was reached the bot uses close prices from each minute.\n", "\n", "For example, if entry price is 100 USD and take_profit_percent is 101.2, the bot will automaticcaly sell when \n", "the close price from last minute is: \n", "\n", "100 USD * 101.2% = 101.2 USD, \n", "\n", "no matter what the technical indicators or candlestick patterns say." ] }, { "cell_type": "code", "execution_count": null, "id": "6989ebb5", "metadata": {}, "outputs": [], "source": [ "take_profit_automatically = True #change to False if you don't want to use take_profit function\n", "take_profit_percent = 101.2 #replace it with the preferred percent of entry price to take profit if the price goes up\n", "\n", "#eg. if entry price is 100$ and take_profit_percent = 101.2, the bot will automaticcaly sell when \n", "#last close price is 100$ * 101.2% = 101.2$, no matter what the technical indicators or candlestick patterns say" ] }, { "cell_type": "markdown", "id": "03643048", "metadata": {}, "source": [ "#### Stop Loss parameters" ] }, { "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": [ "stop_loss_automatically = True #change to False if you don't want to use stop_loss function\n", "stop_loss_percent = 99.5 #replace it with the preferred percent of entry price to stop loss if the price goes down\n", "\n", "#eg. if entry price is 100$ and stop_loss_percent = 99.5, the bot will automaticcaly sell when \n", "#last close price is 100$ * 99.5% = 99.5$, no matter what the technical indicators or candlestick patterns say" ] }, { "cell_type": "markdown", "id": "fb502c0a", "metadata": {}, "source": [ "## Technical Indicators" ] }, { "cell_type": "markdown", "id": "4f655d40", "metadata": {}, "source": [ "Now, we move to technical indicators we will use. \n", "\n", "In last tutorial we were using Relative Strength Index (RSI) and we will use it again here.\n", "\n", "We will also use Bollinger Bands in this bot." ] }, { "cell_type": "markdown", "id": "8cdcf800", "metadata": {}, "source": [ "### Momentum measured with RSI" ] }, { "cell_type": "markdown", "id": "b57e786f", "metadata": {}, "source": [ "#### RSI parameters" ] }, { "cell_type": "markdown", "id": "761a470d", "metadata": {}, "source": [ "We will use Relative Strength Index which is a measure of momentum. \n", "\n", "Here we decide how many prices we want to analyze (how big is the time frame) and what thresholds we want to use. \n", "\n", "The RSI is always a number between 0 and 100, so you can choose any numbers from 0 to 100 for your thresholds and see how it influences the bot's decisions." ] }, { "cell_type": "code", "execution_count": null, "id": "c3d0dbc9", "metadata": {}, "outputs": [], "source": [ "rsi_timeframe = 14 #replace it with preferred timeframe - number of last close prices to calculate RSI (typically = 14)\n", "rsi_oversold_threshold = 30 #replace it with preferred oversold threshold under which bot should buy (typically = 30)\n", "rsi_overbought_threshold = 70 #replace with preferred overbought threshold over which bot should sell (typically = 70)" ] }, { "cell_type": "markdown", "id": "5b1aee39", "metadata": {}, "source": [ "#### RSI function" ] }, { "cell_type": "markdown", "id": "67f78683", "metadata": {}, "source": [ "And thanks to this little function you can decide when do you want RSI to be a signal to buy or sell.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b063b801", "metadata": {}, "outputs": [], "source": [ "def rsi_signals(rsi_now, rsi_oversold_threshold, rsi_overbought_threshold):\n", " print(\"RSI is {:.3f} and RSI thresholds are: {:.3f} - {:.3f}.\".format(rsi_now, \n", " rsi_oversold_threshold, rsi_overbought_threshold))\n", " rsi_signal_buy = rsi_now < rsi_oversold_threshold #use the preferred inequality sign: <, <= \n", " rsi_signal_sell = rsi_now > rsi_overbought_threshold #use the preferred inequality sign: <, <=\n", " \n", " return rsi_signal_buy, rsi_signal_sell" ] }, { "cell_type": "markdown", "id": "4f30aa73", "metadata": {}, "source": [ "### Volatility measured with Bollinger Bands" ] }, { "cell_type": "markdown", "id": "2a00b460", "metadata": {}, "source": [ "#### Bollinger Bands paremeters" ] }, { "cell_type": "markdown", "id": "30bcc169", "metadata": {}, "source": [ "Here you can decide how many prices you want to use for calculating Bollinger Bands (bb_timeperiod) and how many standard deviation above (bb_nbdevup) an below (bb_nbdevdn) from the average should the upper and lower bands be." ] }, { "cell_type": "code", "execution_count": null, "id": "653b1d56", "metadata": {}, "outputs": [], "source": [ "bb_timeperiod = 10 #replace it with preferred timeperiod to calculate Bollinger Bands (typically >= 10)\n", "bb_nbdevup = 2 #replace it with number of standard deviations to calculate upper Bollinger Band (typically = 2)\n", "bb_nbdevdn = 2 #replace it with number of standard deviations to calculate lower Bollinger Band (typically = 2)" ] }, { "cell_type": "markdown", "id": "31d7d83a", "metadata": {}, "source": [ "#### Bollinger Bands function" ] }, { "cell_type": "markdown", "id": "9f332cd4", "metadata": {}, "source": [ "And here you can decide how the signals to buy and sell should be calculated:\n", "- which prices should be taken under consideration\n", "- what kind of inequalities should there be\n", "- which bands should the bot use" ] }, { "cell_type": "code", "execution_count": null, "id": "21a99885", "metadata": {}, "outputs": [], "source": [ "def bb_signals(open_price, high_price, low_price, close_price, last_lowerband, last_middle_band, last_upperband):\n", " if len(data['close'].to_numpy()) > bb_timeperiod:\n", " print(\"Bollinger Bands:\")\n", " print(\"Lower: {:.3f}, Middle: {:.3f}, Upper: {:.3f}.\".format(last_lowerband, last_middle_band, last_upperband))\n", " #BB signal to buy\n", " bb_signal_buy = (close_price #replace with the price you like: open_price, high_price, low_price, close_price \n", " < #replace with inequality sign you like: <, >, <=, >=, change parentheses if necessary\n", " last_lowerband) #replace with the band you like: last_lowerband, last_middle_band, last_upperband\n", "\n", " #BB signal to sell\n", " bb_signal_sell = (close_price #replace with the price you like: open_price, high_price, low_price, close_price \n", " > #replace with inequality sign you like: <, >, <=, >=, change parentheses if necessary\n", " last_upperband) #replace with the band you like: last_lowerband, last_middle_band, last_upperband\n", " \n", " return bb_signal_buy, bb_signal_sell" ] }, { "cell_type": "markdown", "id": "d28d1a96", "metadata": {}, "source": [ "### Technical indicators final signal logic" ] }, { "cell_type": "markdown", "id": "a89e08f7", "metadata": {}, "source": [ "#### TI final signal logic function" ] }, { "cell_type": "markdown", "id": "f434820e", "metadata": {}, "source": [ "And here you can decide when the technical indicators should send signal to buy or to sell. You can make the bot more or less cautious.\n", "\n", "You may want to buy when only one of indicators tells you to buy or to sell:\n", "\n", "RSI or BB \n", "\n", "or when both of them tell you that:\n", "\n", "RSI and BB. \n", "\n", "You can change it by simply changing the word 'or' to 'and' in the condition you want.\n", "\n", "If either of the conditions is met, your bot will tell you that it's not sure whether you should buy or sell. \n", "\n", "We will use these signals together with candlestick signals, so this function is not yet final trading logic." ] }, { "cell_type": "code", "execution_count": null, "id": "fc6ce3c9", "metadata": {}, "outputs": [], "source": [ "def ti_logic(rsi_signal, bb_signal):\n", "\n", " #TI signal to buy\n", " if (rsi_signal == 'buy' \n", " or #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " bb_signal == 'buy'): \n", " ti_signal = \"buy\"\n", "\n", " #TI signal to sell\n", " elif (rsi_signal == 'sell' \n", " or #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " bb_signal == 'sell'): \n", " ti_signal = \"sell\"\n", "\n", " #TI signal to do nothing \n", " else: \n", " ti_signal = \"I'm not sure\"\n", " \n", " return ti_signal" ] }, { "cell_type": "markdown", "id": "75df1bdd", "metadata": {}, "source": [ "### Candlestick Patterns final signal logic" ] }, { "cell_type": "markdown", "id": "09611d81", "metadata": {}, "source": [ "We will be using 4 candlestick patterns here.\n", "\n", "The two patterns will be telling us if we should buy (hammer and morning star) and the other 2 if we should sell (shooting star and evening star).\n", "\n", "So, as with technical indicators, we can make our bot more or less cautios when it buys or sells by simply changing the conditions that should be met.\n", "\n", "So, the candlestick may tell the bot to buy, for example, if the hammer tells us to buy or the morning star tells us to buy and the same time the shooting star is not telling us to sell and the evening star is not telling us to sell.\n", "\n", "As with technical indicators you can change that logic by changing the words: 'and' to 'or' or 'or' to 'and'. Just remember about parentheses when you change that logic.\n", "\n", "And the candlestick may send a signal to sell, when shooting star tells us to sell or evening star tells us to sell and neither hammer nor morning star tell us to buy.\n", "\n", "Just remember that it's not a final signal for our bot to buy or sell. This signal will be combined with signal from technical indicators." ] }, { "cell_type": "markdown", "id": "c8301a1b", "metadata": {}, "source": [ "#### Candlestick Pattern final signal logic function" ] }, { "cell_type": "code", "execution_count": null, "id": "319b7271", "metadata": {}, "outputs": [], "source": [ "def candlestick_logic(hammer_signal, morning_star_signal, shooting_star_signal, evening_star_signal):\n", " if ((hammer_signal == 'buy' \n", " or #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " morning_star_signal == 'buy') \n", " and #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " shooting_star_signal != 'sell' \n", " and #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " evening_star_signal != 'sell'):\n", " candlestick_signal = \"buy\"\n", " \n", " elif ((hammer_signal != 'buy' \n", " and #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " morning_star_signal != 'buy') \n", " and #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " (shooting_star_signal == 'sell' \n", " or #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " evening_star_signal == 'sell')):\n", " candlestick_signal = \"sell\"\n", " else:\n", " candlestick_signal = \"I'm not sure\"\n", " \n", " return candlestick_signal" ] }, { "cell_type": "markdown", "id": "5bddccfe", "metadata": {}, "source": [ "### Final Trade Signal Logic" ] }, { "cell_type": "markdown", "id": "e85dee16", "metadata": {}, "source": [ "So, our final trade signal will be calculated with both: technical indicators signal and candlestick patterns signal.\n", "\n", "And, like previously, we can change that logic.\n", "\n", "In here, at first we decide that our bot will get the signal to wait (I'm not sure) if the signals are contradictory, so if one tells to buy and the other to sell, we don't do anything.\n", "\n", "And we can buy when both our signals tell us to buy or when only one of them tells us to buy.\n", "\n", "And we can sell when both our signals tell us to sell or when only on of them tells us to sell." ] }, { "cell_type": "markdown", "id": "2b34afef", "metadata": {}, "source": [ "#### Final trade signal function" ] }, { "cell_type": "code", "execution_count": null, "id": "75730b90", "metadata": {}, "outputs": [], "source": [ "def calculate_final_trade_signal(ti_signal, candlestick_signal):\n", " if ((ti_signal == 'buy' \n", " and #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " candlestick_signal == 'sell') \n", " or #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " ti_signal == 'sell' \n", " and #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " candlestick_signal == 'buy'): \n", " final_trade_signal = \"I'm not sure\"\n", " \n", " elif (ti_signal == 'buy' \n", " or #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " candlestick_signal == 'buy'): #with more cautious version it would be: 'and' \n", " final_trade_signal = \"buy\" \n", " \n", " elif (ti_signal == 'sell' \n", " or #replace with logic you want: 'and', 'or', 'and not', just remember to change parentheses if necessary\n", " candlestick_signal == 'sell'): #with less cautious version it would be: 'and' \n", " final_trade_signal = \"sell\"\n", " \n", " else:\n", " final_trade_signal = \"I'm not sure\"\n", " \n", " print(\"Final trade signal:\", final_trade_signal)\n", " print()\n", " \n", " return final_trade_signal" ] }, { "cell_type": "markdown", "id": "fda5d183", "metadata": {}, "source": [ "And that's it about signals. Now, the rest of the code help us make this bot working, but we don't need to change there anything." ] }, { "cell_type": "markdown", "id": "d826ebec", "metadata": {}, "source": [ "# The rest of the code you don't have to change, but might change if you know how to do it to make it work better for you" ] }, { "cell_type": "markdown", "id": "649e15fb", "metadata": {}, "source": [ "### Install and update all the neccessary libraries and packages" ] }, { "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": {}, "outputs": [], "source": [ "!pip install alpaca-trade-api --upgrade\n", "#https://github.com/alpacahq/alpaca-trade-api-python" ] }, { "cell_type": "code", "execution_count": null, "id": "01a39b39", "metadata": {}, "outputs": [], "source": [ "!pip install mplfinance --upgrade\n", "#https://github.com/matplotlib/mplfinance" ] }, { "cell_type": "code", "execution_count": null, "id": "2fdbc9bb", "metadata": {}, "outputs": [], "source": [ "!pip install numpy --upgrade\n", "#https://pypi.org/project/numpy/" ] }, { "cell_type": "code", "execution_count": null, "id": "b892dfce", "metadata": {}, "outputs": [], "source": [ "!pip install pandas --upgrade\n", "#https://pandas.pydata.org/getting_started.html" ] }, { "cell_type": "code", "execution_count": null, "id": "6acffeeb", "metadata": {}, "outputs": [], "source": [ "!pip install ta-lib --upgrade\n", "#https://github.com/mrjbq7/ta-lib\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": "4695fc72", "metadata": {}, "outputs": [], "source": [ "!pip install websocket-client --upgrade\n", "#https://pypi.org/project/websocket-client/" ] }, { "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": [ "import alpaca_trade_api as tradeapi\n", "import ast\n", "import json\n", "import matplotlib\n", "import mplfinance as mpf\n", "import numpy as np\n", "import pandas as pd\n", "import sys\n", "import talib as ta\n", "import websocket" ] }, { "cell_type": "markdown", "id": "1477cf28", "metadata": {}, "source": [ "#### Check the versions of the tools you use" ] }, { "cell_type": "markdown", "id": "717b9fc5", "metadata": {}, "source": [ "And with this cell you can check what versions of the libraries and packages you have. \n", "\n", "Python is great because people are creating and improving their libraries, but sometimes the newer version of the library might lose some functionality from previous versions and the older programs which were using older versions of the given library might not work properly. Therefore, it's good to know what versions we have." ] }, { "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(\"mplfinance version: {}\".format(mpf.__version__))\n", "print(\"numpy version: {}\".format(np.__version__))\n", "print(\"pandas version: {}\".format(pd.__version__))\n", "print(\"talib version: {}\".format(ta.__version__))\n", "print(\"websocket version: {}\".format(websocket.__version__))" ] }, { "cell_type": "markdown", "id": "5384aee5", "metadata": {}, "source": [ "### Connect to Alpaca REST api" ] }, { "cell_type": "markdown", "id": "71e52b31", "metadata": {}, "source": [ "Now we can connect to Alpaca's REST api with which we will be executing orders to buy or sell.\n", "\n", "We use here previously created variables, so in here we don't have to change anything even if we change our keys." ] }, { "cell_type": "code", "execution_count": null, "id": "d58fe858", "metadata": {}, "outputs": [], "source": [ "api = tradeapi.REST(KEY_ID, SECRET_KEY, APCA_API_BASE_URL, \"v2\")" ] }, { "cell_type": "markdown", "id": "1ec85c95", "metadata": {}, "source": [ "### Dataframe for collecting stock data" ] }, { "cell_type": "markdown", "id": "9334059d", "metadata": {}, "source": [ "Now, in order to calculate all the indicators we will be using live data from real stock market. We will be collecting data after every minute and will get: \n", "- open price of each minute\n", "- high price of each minute\n", "- low price of each minute\n", "- close price of each minute\n", "- volume of the shares that were traded during that minute\n", "\n", "In here we create a dataframe which for now will only have names of the columns and a bunch of zeros in the first line. We will be filling in that dataframe in just a couple of minutes." ] }, { "cell_type": "code", "execution_count": null, "id": "a093987c", "metadata": {}, "outputs": [], "source": [ "columns = ('time','open','high','low','close','volume')\n", "data = pd.DataFrame([[0,0,0,0,0,0]], columns=columns)" ] }, { "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 only once here at the beginning of the trading session.\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": [ "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": "8d1a4595", "metadata": {}, "source": [ "### Placeholders used in calculating trading signals" ] }, { "cell_type": "markdown", "id": "ff5f74f4", "metadata": {}, "source": [ "Now, here we have some placeholders for variables that will be calculated by our bot after it gets some data. So, there is nothing to change here, just to copy to your bot." ] }, { "cell_type": "markdown", "id": "6ce3822b", "metadata": {}, "source": [ "rsi_now placeholder (don't change it)" ] }, { "cell_type": "code", "execution_count": null, "id": "0d1662c8", "metadata": {}, "outputs": [], "source": [ "rsi_now = 50 #don't change it, this is just a placholder, the real value will be calculated later" ] }, { "cell_type": "markdown", "id": "aea3da46", "metadata": {}, "source": [ "Some Bollinger Bands placeholders (don't change them)" ] }, { "cell_type": "code", "execution_count": null, "id": "f15966ec", "metadata": {}, "outputs": [], "source": [ "open_price, high_price, low_price, close_price = (0,0,0,0) #don't change it, the real values will be calculated later\n", "last_lowerband, last_middle_band, last_upperband = (0,0,0) #don't change it, the real values will be calculated later" ] }, { "cell_type": "markdown", "id": "039f94d9", "metadata": {}, "source": [ "TI placeholders (don't change them)" ] }, { "cell_type": "code", "execution_count": null, "id": "39bc9a80", "metadata": {}, "outputs": [], "source": [ "rsi_signal = \"I'm not sure\" #don't change it, this is just a placholder, the real value will be calculated later\n", "bb_signal = \"I'm not sure\" #don't change it, this is just a placholder, the real value will be calculated later" ] }, { "cell_type": "markdown", "id": "9edbbcfa", "metadata": {}, "source": [ "Candlestick Patterns placeholders (don't change them)" ] }, { "cell_type": "code", "execution_count": null, "id": "da303e9e", "metadata": {}, "outputs": [], "source": [ "hammer_signal = \"I'm not sure\" #don't change it, this is just a placholder, the real value will be calculated later\n", "morning_star_signal = \"I'm not sure\" #don't change it, the real value will be calculated later\n", "shooting_star_signal = \"I'm not sure\" #don't change it, the real value will be calculated later\n", "evening_star_signal = \"I'm not sure\" #don't change it, the real value will be calculated later" ] }, { "cell_type": "markdown", "id": "2bb07555", "metadata": {}, "source": [ "FTSL placeholders" ] }, { "cell_type": "code", "execution_count": null, "id": "d3bcd672", "metadata": {}, "outputs": [], "source": [ "ti_signal = \"I'm not sure\"\n", "candlestick_signal = \"I'm not sure\"" ] }, { "cell_type": "markdown", "id": "5d93bda5", "metadata": {}, "source": [ "### Calculate Technical Indicators signal" ] }, { "cell_type": "markdown", "id": "27c4c37d", "metadata": {}, "source": [ "Now, we can create a function that will actually calculate the signal from technical indicators.\n", "\n", "This functions takes four arguments to make it easier to understand: all the data from our dataframe, and high, low and close price from last minute.\n", "\n", "At first we calculate RSI signal when we have enough data. We use TA-lib library for that, so it's just couple of lines of code.\n", "\n", "Here we use previously declared thresholds for RSI and rsi_signals function.\n", "\n", "Then we print the signal, so we know what the signal was.\n", "\n", "After calculating RSI signal, we calculate Bollinger Bands Signal.\n", "\n", "We use all the data we collected during this session that we have in our dataframe to calculate lower, middle, and upper band. Here we use previously declared timeperiod and number of standard deviations.\n", "\n", "Then we use prices from last minute to check whether we are between or outside the bands and create a signal to buy or sell based on that.\n", "\n", "Finally, we calculate the final signal from technical indicators with the previously declared logic." ] }, { "cell_type": "code", "execution_count": null, "id": "1b16a648", "metadata": {}, "outputs": [], "source": [ "def calculate_ti_signal(data, high_price, low_price, close_price): \n", " #CALCULATING TECHNICAL INDICATORS SIGNALS \n", " #Calculating RSI Signal\n", " closed = data['close'].to_numpy()\n", " if len(closed) > rsi_timeframe:\n", " close_prices = np.array(closed[1:], float)\n", " rsis = ta.RSI(close_prices, rsi_timeframe)\n", " rsi_now = rsis[-1]\n", "\n", " rsi_signal_buy, rsi_signal_sell = rsi_signals(rsi_now, rsi_oversold_threshold, rsi_overbought_threshold)\n", " if rsi_signal_buy: rsi_signal = \"buy\" \n", " elif rsi_signal_sell: rsi_signal = \"sell\" \n", " else: rsi_signal = \"I'm not sure\"\n", " else:\n", " rsi_signal = \"too little data to calculate RSI\"\n", "\n", " print(\"RSI Signal:\", rsi_signal)\n", " print()\n", "\n", " #Calculating Bollinger Bands Signal\n", " upperband, middleband, lowerband = ta.BBANDS(data[\"close\"][1:], \n", " timeperiod=bb_timeperiod, nbdevup=bb_nbdevup, nbdevdn=bb_nbdevdn, matype=0)\n", " last_lowerband = float(lowerband.iloc[-1])\n", " last_middleband = float(middleband.iloc[-1])\n", " last_upperband = float(upperband.iloc[-1])\n", "\n", " bb_signal_buy, bb_signal_sell = bb_signals(open_price, high_price, low_price, close_price, \n", " last_lowerband, last_middleband, last_upperband)\n", " if bb_signal_buy: bb_signal = \"buy\" \n", " elif bb_signal_sell: bb_signal = \"sell\" \n", " else: bb_signal = \"I'm not sure\"\n", "\n", " print(\"Bollinger Bands Signal:\", bb_signal)\n", " print()\n", "\n", " #Calculating general signal from technical indicators\n", " ti_signal = ti_logic(rsi_signal, bb_signal)\n", " \n", " print(\"Technical Indicators signal:\", ti_signal)\n", " print()\n", " \n", " return ti_signal" ] }, { "cell_type": "markdown", "id": "0521055a", "metadata": {}, "source": [ "### Calculate Candlestick Patterns signal" ] }, { "cell_type": "markdown", "id": "6b10c234", "metadata": {}, "source": [ "Now we calculate the Candlestick Patterns signal.\n", "\n", "We check whether any of the four patterns appeared. We use all the data we collected in our dataframe and TA-lib to do that. As you can see, we just need one line of code for each pattern. And because we calculate all the patterns in the database we get the last one to calculate this minutes signal.\n", "\n", "After we calculate all 4 signals, we use previously declared logic to calculate final Candlestick Patterns signal as buy, sell or wait (I'm not sure)." ] }, { "cell_type": "code", "execution_count": null, "id": "2673b3f0", "metadata": {}, "outputs": [], "source": [ "def calculate_candlestick_signal(data):\n", " #CALCULATING CANDLESTICK PATTERNS AND SIGNALS\n", " #Hammer\n", " hammer = ta.CDLHAMMER(data[\"open\"][1:], data[\"high\"][1:], data[\"low\"][1:], data[\"close\"][1:])\n", " last_hammer = hammer.iloc[-1]\n", " if last_hammer != 0: hammer_signal = \"buy\"\n", " else: hammer_signal = \"I'm not sure\"\n", " print(\"Last Hammer Signal:\", hammer_signal)\n", "\n", " #Shooting Star\n", " shooting_star = ta.CDLSHOOTINGSTAR(data[\"open\"][1:], data[\"high\"][1:], data[\"low\"][1:], data[\"close\"][1:])\n", " last_shooting_star = shooting_star.iloc[-1]\n", " if last_shooting_star != 0: shooting_star_signal = \"sell\"\n", " else: shooting_star_signal = \"I'm not sure\"\n", " print(\"Last Shooting Star Signal:\", shooting_star_signal)\n", " \n", " #Morning Star\n", " morning_star = ta.CDLMORNINGSTAR(data[\"open\"][1:], data[\"high\"][1:], data[\"low\"][1:], data[\"close\"][1:])\n", " last_morning_star = morning_star.iloc[-1]\n", " if last_morning_star != 0: morning_star_signal = \"buy\"\n", " else: morning_star_signal = \"I'm not sure\"\n", " print(\"Last Morning Star Signal:\", morning_star_signal)\n", " \n", " #Evening Star\n", " evening_star = ta.CDLEVENINGSTAR(data[\"open\"][1:], data[\"high\"][1:], data[\"low\"][1:], data[\"close\"][1:])\n", " last_evening_star = evening_star.iloc[-1]\n", " if last_evening_star != 0: evening_star_signal = \"sell\"\n", " else: evening_star_signal = \"I'm not sure\"\n", " print(\"Last Evening Star Signal:\", evening_star_signal)\n", " \n", " #Calculating general signal from candlestick patterns\n", " candlestick_signal = candlestick_logic(hammer_signal,morning_star_signal,shooting_star_signal,evening_star_signal)\n", " \n", " print(\"Candlestick signal:\", candlestick_signal)\n", " print()\n", " \n", " return candlestick_signal" ] }, { "cell_type": "markdown", "id": "872fa109", "metadata": {}, "source": [ "### 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": "1b5f08ad", "metadata": {}, "outputs": [], "source": [ "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 * take_profit_percent / 100:\n", " n_shares = int(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(take_profit_percent, aep, asset, aep * take_profit_percent / 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(take_profit_percent, aep, asset, aep * take_profit_percent / 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": "0b44c3b4", "metadata": {}, "source": [ "### 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": "4b69f510", "metadata": {}, "outputs": [], "source": [ "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 * stop_loss_percent / 100:\n", " n_shares = int(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(stop_loss_percent, aep, asset, aep * stop_loss_percent / 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(stop_loss_percent, aep, asset, aep * stop_loss_percent / 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": "23e6071b", "metadata": {}, "source": [ "### Check if market is open" ] }, { "cell_type": "markdown", "id": "2c177798", "metadata": {}, "source": [ "In this function we check if the market is open.\n", "\n", "We use Alpaca's API to do it, so it's really simple.\n", "\n", "It's not necessary for the bot to run, but it gives us information if the market is open or closed at the moment.\n", "\n", "It also makes the bot stop running if the market is closed, whether it's before market opens or just after it closes." ] }, { "cell_type": "code", "execution_count": null, "id": "e3d7364f", "metadata": {}, "outputs": [], "source": [ "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'l 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": "8005a9eb", "metadata": {}, "source": [ "### Print openning messages" ] }, { "cell_type": "markdown", "id": "f53fab52", "metadata": {}, "source": [ "Now, I believe it's good to know what our bot does during trading, so here is a bunch of information that will be printed just after we run the bot, so we can check if everything is set as we wanted it." ] }, { "cell_type": "code", "execution_count": null, "id": "ca4df781", "metadata": {}, "outputs": [], "source": [ "def print_openning_messages(): \n", " print(\"We have {:.2f}$ cash.\".format(cash))\n", " print(\"Our Buying Power is: {:.2f}$\".format(buying_power))\n", " print(\"We will use {}% of our cash for trading.\".format(funds_percentage))\n", " print(\"Funds we will use for trading: {:.2f}$\\n\".format(funds))\n", " print(\"I will be trading {}.\".format(asset))\n", " print(\"I will be buying as many whole shares of {} as I can with the {:.2f}$ you told me to use.\\n\"\n", " .format(asset, funds))\n", " print(\"I will calculate RSI with {}-min timeframe. The thresholds are set to: oversold = {} and overbought = {}\"\n", " .format(rsi_timeframe, rsi_oversold_threshold, rsi_overbought_threshold))\n", " print(\"I will analyze Bollinger Bands with {}-min timeframe, {} standard deviations up and {} down.\"\n", " .format(bb_timeperiod, bb_nbdevup, bb_nbdevdn))\n", " print(\"I will also be checking for candlestick patterns.\")\n", " print(\"The candlestick patterns we use are: Hammer, Shooting Star, Morning Star, Evening Star.\\n\")\n", " \n", " if take_profit_automatically:\n", " print(\"Take profit is set to {}% from the average entry price.\".format(take_profit_percent))\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 automaticcaly sell when last close price is more than 100$*{}%={:.2f}$\"\n", " .format(take_profit_percent, 100*take_profit_percent/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))\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 automaticcaly sell when last close price is less than 100$*{}%={:.2f}$\"\n", " .format(stop_loss_percent, 100*stop_loss_percent/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.\")" ] }, { "cell_type": "markdown", "id": "91eec43a", "metadata": {}, "source": [ "### Function to run after connecting to Alpaca (on_open function) " ] }, { "cell_type": "markdown", "id": "46bce181", "metadata": {}, "source": [ "And now we can create a function to run after our bot connects to Alpaca.\n", "\n", "At first we send an authenticate message with our Key ID and Secret Key.\n", "\n", "Then we send a message to collect the data after each minute of the asset we declared in the asset variable.\n", "\n", "Thanks to this \"bars\" we type here, we get: timestamp of last minute, open, high, low, close prices, and the volume of traded shares from last minute that our bot will be using to calculate signals to buy or sell shares of the chosen asset.\n", "\n", "And we print the statements we prepared previously and some more." ] }, { "cell_type": "code", "execution_count": null, "id": "674bff75", "metadata": {}, "outputs": [], "source": [ "def on_open(SuperAI_trader):\n", " authenticate_message = {\"action\":\"auth\", \"key\":KEY_ID, \"secret\":SECRET_KEY}\n", " SuperAI_trader.send(json.dumps(authenticate_message))\n", " \n", " listen_message = {\"action\":\"subscribe\", \"bars\": [asset]}\n", " SuperAI_trader.send(json.dumps(listen_message))\n", " print(\"I'm connected to Alpaca API and ready to work. I'm starting to watch the prices.\\n\")\n", " print_openning_messages()\n", " print(\"\\nSo, here we go. Wish me luck.\\n\")\n", " print(\"* * * * * * * * * * * * * * * * * * * * * * * * *\\n\")" ] }, { "cell_type": "markdown", "id": "f8fc6bd4", "metadata": {}, "source": [ "### Function to run after every message from Alpaca (on_message function)" ] }, { "cell_type": "markdown", "id": "b460d500", "metadata": {}, "source": [ "And here is the final function before we run the bot.\n", "\n", "This function will be executed after every message we get from Alpaca.\n", "\n", "So, here we check if the market is open. If it's closed, the bot will tell us about it and stop running.\n", "\n", "Then we take the data we get from Alpaca (time, open price, high price, low price, close price, and volume) and put it into our dataframe.\n", "\n", "We also create three variables called high, low and close price to make it all easier to read and use.\n", "\n", "After we update our dataframe, we check if we have any shares of the asset and print info about it.\n", "\n", "Then we use our functions to calculate final trade signal.\n", "\n", "We set a 'sell order filled' variable to False for now.\n", "\n", "And we check what the final trade signal is.\n", "\n", "If the signal is to buy and we don't have any shares yet, we try to buy as many shares as we can with the funds we declared.\n", "\n", "If the signal is to sell and we have some shares, we try to sell all the shares we have. If we do submit an order to sell, we change our 'sell order filled' variable to True, so we don't try to sell them again with our take_profit or stop_loss functions.\n", "\n", "And now we run the take_profit and stop_loss functions.\n", "\n", "And that's it. After that the bot will wait for another message to start this function from the beginning: check if market is open, collect the data, calculate trade signals, submit proper orders." ] }, { "cell_type": "code", "execution_count": null, "id": "9e46980c", "metadata": {}, "outputs": [], "source": [ "def on_message(SuperAI_trader, message):\n", " check_if_market_open() \n", " #Adding new data to the dataset\n", " global data\n", " bar = ['time', 0.0, 0.0, 0.0, 0.0, 0] \n", " \n", " astmessage = ast.literal_eval(message)\n", " if astmessage[0]['S'] == asset:\n", " bar[0] = astmessage[0].get(\"t\")\n", " bar[1] = astmessage[0].get(\"o\")\n", " bar[2] = astmessage[0].get(\"h\")\n", " bar[3] = astmessage[0].get(\"l\") \n", " bar[4] = astmessage[0].get(\"c\") \n", " bar[5] = astmessage[0].get(\"v\")\n", " high_price = bar[2]\n", " low_price = bar[3]\n", " close_price = bar[4]\n", " \n", " bar_to_df = pd.DataFrame([bar], columns=columns)\n", " if bar[0] != 'time':\n", " data = pd.concat([data, bar_to_df])\n", " print(\"EST time:\", str(pd.to_datetime(bar[0][:16]).tz_localize('UTC').tz_convert('EST'))[:16])\n", " print(\"Close price of {}: {}$\".format(asset, close_price))\n", " \n", " try:\n", " position = api.get_position(asset)\n", " n_shares = int(position.qty)\n", " print(\"We have {} shares of {}.\\n\".format(n_shares, asset))\n", " except:\n", " pass\n", " \n", " #Calculate technical analysis signal\n", " ti_signal = calculate_ti_signal(data, high_price, low_price, close_price)\n", "\n", " #Calculate candlestick patterns signal\n", " candlestick_signal = calculate_candlestick_signal(data)\n", " \n", " #Calculate final trade signal\n", " final_trade_signal = calculate_final_trade_signal(ti_signal, candlestick_signal)\n", " \n", " #Execute action after recieving the final trade signal: submitting an order\n", " sell_order_filled = False\n", " if final_trade_signal == \"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", " n_shares = funds // close_price\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 == \"sell\":\n", " try:\n", " position = api.get_position(asset)\n", " n_shares = int(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\")" ] }, { "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": [ "## Running the bot" ] }, { "cell_type": "markdown", "id": "3c612592", "metadata": {}, "source": [ "We create our Trader with the socket address, on_open and on_message functions that we previously created and we tell the bot to run forever.\n", "\n", "It will start running, check if the market is open, and if so run until the market closes. And if the market is closed, our bot will tell us about it.\n", "\n", "During the trading our bot will be telling us after every minute what happens thanks to the print statements we've created." ] }, { "cell_type": "code", "execution_count": null, "id": "be358ba9", "metadata": { "scrolled": true }, "outputs": [], "source": [ "SuperAI_trader = websocket.WebSocketApp(socket, on_open=on_open, on_message=on_message)\n", "SuperAI_trader.run_forever()\n", "print(\"You've interrupted me. That's it then. I hope I did good. Till the next time.\")" ] }, { "cell_type": "markdown", "id": "41a29418", "metadata": {}, "source": [ "# Analysis of the collected data for future bot improvements" ] }, { "cell_type": "markdown", "id": "cac79a3d", "metadata": {}, "source": [ "After the bot runs for some time, we can save all the data it collected to the csv file, so we can check it later and analyze it when we have time for that.\n", "\n", "So, if you did run the bot and you have that data, but don't want to analyze anything at the moment, run only the two following cells now to save the data you got. \n", "\n", "Otherwise, after you close your program, the dataframe with all the data you have now will be cleaned and it won't have that data anymore." ] }, { "cell_type": "markdown", "id": "12c7b971", "metadata": {}, "source": [ "In this cell we declare the name of the file we want to create. You should probably change it every time you get new data, because otherwise you will overwrite the previous file." ] }, { "cell_type": "code", "execution_count": null, "id": "159c6a61", "metadata": {}, "outputs": [], "source": [ "data_csv_file = \"live_data_from_the_last_session.csv\" #you might want to change it" ] }, { "cell_type": "markdown", "id": "7c0801db", "metadata": {}, "source": [ "And in this cell we actually save the data from pandas dataframe to the csv file." ] }, { "cell_type": "code", "execution_count": null, "id": "dfa0e848", "metadata": {}, "outputs": [], "source": [ "data.to_csv(data_csv_file, header = True)" ] }, { "cell_type": "markdown", "id": "a0e13fd0", "metadata": {}, "source": [ "Now, in the cell below, we read the data from that file and clean up the data, so to make it more readable. You can check how the data looks like before it, if you want and then after.\n", "\n", "At first here we drop the first line of the data and the first column, then we reset the indexes of the dataframe.\n", "\n", "Then, we convert the time of the messages we got from Alpaca to East Standard Time (the time in New York City). It will make it easier to read.\n", "\n", "And we set our time column to be an index in our dataframe. We do it, so we can create a cool chart with all that data.\n", "\n", "And finally, we print out our dataframe, so we can see how it looks like right now." ] }, { "cell_type": "code", "execution_count": null, "id": "309e29cf", "metadata": {}, "outputs": [], "source": [ "data_csv = pd.read_csv(data_csv_file)\n", "data_csv = data_csv.drop([0], axis=0)\n", "data_csv = data_csv.drop(data_csv.columns[0], axis=1)\n", "data_csv.reset_index(drop = True, inplace = True)\n", "data_csv.time = [pd.to_datetime(data_csv.time[x][:16]).tz_localize('UTC').tz_convert('EST') \n", " for x in range(len(data_csv.time))]\n", "data_csv = data_csv.set_index(\"time\")\n", "data_csv" ] }, { "cell_type": "markdown", "id": "0bc7e78a", "metadata": {}, "source": [ "Now, in the next six cells, we add to our dataframe all the indicators and candlestick patterns we calculate with the data we have in the dataframe (open, high, low, close prices and volume)." ] }, { "cell_type": "code", "execution_count": null, "id": "2812bb4b", "metadata": {}, "outputs": [], "source": [ "rsi_to_csv = ta.RSI(data_csv[\"close\"], rsi_timeframe)\n", "data_csv[\"rsi\"] = rsi_to_csv" ] }, { "cell_type": "code", "execution_count": null, "id": "fddba219", "metadata": {}, "outputs": [], "source": [ "upperband, middleband, lowerband = ta.BBANDS(data_csv[\"close\"], timeperiod=bb_timeperiod, \n", " nbdevup=bb_nbdevup, nbdevdn=bb_nbdevdn, matype=0)\n", "data_csv[\"upperbb\"] = upperband\n", "data_csv[\"middlebb\"] = middleband\n", "data_csv[\"lowerbb\"] = lowerband" ] }, { "cell_type": "code", "execution_count": null, "id": "cf22a903", "metadata": {}, "outputs": [], "source": [ "hammer_to_csv = ta.CDLHAMMER(data_csv[\"open\"], data_csv[\"high\"], data_csv[\"low\"], data_csv[\"close\"])\n", "data_csv[\"hammer\"] = hammer_to_csv" ] }, { "cell_type": "code", "execution_count": null, "id": "cf6b944f", "metadata": {}, "outputs": [], "source": [ "shooting_star_to_csv = ta.CDLSHOOTINGSTAR(data_csv[\"open\"], data_csv[\"high\"], data_csv[\"low\"], data_csv[\"close\"])\n", "data_csv[\"shooting_star\"] = shooting_star_to_csv" ] }, { "cell_type": "code", "execution_count": null, "id": "c7e83023", "metadata": {}, "outputs": [], "source": [ "morning_star = ta.CDLMORNINGSTAR(data_csv[\"open\"], data_csv[\"high\"], data_csv[\"low\"], data_csv[\"close\"])\n", "data_csv[\"morning star\"] = morning_star" ] }, { "cell_type": "code", "execution_count": null, "id": "90b327b9", "metadata": {}, "outputs": [], "source": [ "evening_star = ta.CDLEVENINGSTAR(data_csv[\"open\"], data_csv[\"high\"], data_csv[\"low\"], data_csv[\"close\"])\n", "data_csv[\"evening star\"] = evening_star" ] }, { "cell_type": "markdown", "id": "822decfd", "metadata": {}, "source": [ "And we can check how it looks like now." ] }, { "cell_type": "code", "execution_count": null, "id": "7f53ae88", "metadata": {}, "outputs": [], "source": [ "data_csv" ] }, { "cell_type": "markdown", "id": "2542665b", "metadata": {}, "source": [ "Now, that we have our dataframe ready, we can plot the chart using matplotlib finance library. This library allows us to plot different types of plots, but we will only plot the one with candlesticks here. And we will use 'yahoo' style for the plot.\n", "\n", "If you want, you can check other types and styles of plots and see how they look like." ] }, { "cell_type": "code", "execution_count": null, "id": "901e0ef5", "metadata": {}, "outputs": [], "source": [ "mpf.plot(data_csv, figratio=(2,1), type='candle', title='last session', \n", " tight_layout=True, style='yahoo')" ] }, { "cell_type": "markdown", "id": "5699dd28", "metadata": {}, "source": [ "As you can see, we have our candlesticks on the plot at the moment. \n", "\n", "Now we will add the RSI and Bollinger Bands to our plot.\n", "\n", "At first we create two variables with information which will be printed on the plot." ] }, { "cell_type": "code", "execution_count": null, "id": "7abbfc38", "metadata": {}, "outputs": [], "source": [ "rsi_l = \"RSI (timeframe = {})\".format(rsi_timeframe)\n", "bb_title = \"BB (timeframe = {}, lower standard deviation = {}, upper standard deviation = {})\".format(bb_timeperiod,\n", " bb_nbdevup, bb_nbdevdn)" ] }, { "cell_type": "markdown", "id": "d486af38", "metadata": {}, "source": [ "And now we create the additional plots, add these plots to the basic one and plot everything." ] }, { "cell_type": "code", "execution_count": null, "id": "8c81d38e", "metadata": {}, "outputs": [], "source": [ "ti_plot = [mpf.make_addplot(data_csv['rsi'], color='black', width=0.5, panel=1, ylabel=rsi_l),\n", " mpf.make_addplot(data_csv['upperbb'],color='b', title=bb_title, width=0.5), \n", " mpf.make_addplot(data_csv['lowerbb'],color='b', width=0.5)]\n", "mpf.plot(data_csv, figratio=(2,1), type='candle', title='last session with RSI (black) and Bollinger Bands (blue)', \n", " tight_layout=True, style='yahoo',addplot=ti_plot)" ] }, { "cell_type": "markdown", "id": "dd75c8ae", "metadata": {}, "source": [ "And now we can check in our dataframe for the situations when our trading signals were telling us to buy or sell." ] }, { "cell_type": "markdown", "id": "5a3b29e9", "metadata": {}, "source": [ "So, at first we check when the RSI was lower than the oversold threshold we declared." ] }, { "cell_type": "code", "execution_count": null, "id": "f2d433c1", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"rsi\"] < rsi_oversold_threshold] #you might change the RSI to the one you want to use in comparison" ] }, { "cell_type": "markdown", "id": "60f7c572", "metadata": {}, "source": [ "Now we can see when the RSI was higher then the overbought threshold we declared." ] }, { "cell_type": "code", "execution_count": null, "id": "7f4ca9eb", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"rsi\"] > rsi_overbought_threshold] #you might change the RSI to the one you want to use in comparison" ] }, { "cell_type": "markdown", "id": "baef935b", "metadata": {}, "source": [ "Here we check when our close prices were lower than lower Bollinger Band created with the parameters we declared (timeframe, standard deviations)." ] }, { "cell_type": "code", "execution_count": null, "id": "1e022884", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"close\"] < data_csv[\"lowerbb\"]] #you might change the columns you want to compare" ] }, { "cell_type": "markdown", "id": "ee0ad02f", "metadata": {}, "source": [ "And here we check when our close prices were higher than higher Bollinger Band created with the parameters we declared (timeframe, standard deviations)." ] }, { "cell_type": "code", "execution_count": null, "id": "ca8e9500", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"close\"] > data_csv[\"upperbb\"]] #you might change the columns you want to compare" ] }, { "cell_type": "markdown", "id": "528c5f7a", "metadata": {}, "source": [ "Here we can see when the Hammer signal was active." ] }, { "cell_type": "code", "execution_count": null, "id": "94192d4a", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"hammer\"] != 0]" ] }, { "cell_type": "markdown", "id": "481efc26", "metadata": {}, "source": [ "Here we can see when the Shooting Star signal was active." ] }, { "cell_type": "code", "execution_count": null, "id": "5cd2e5d6", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"shooting_star\"] != 0]" ] }, { "cell_type": "markdown", "id": "b1b47222", "metadata": {}, "source": [ "Here we can see all the data with morning star detected." ] }, { "cell_type": "code", "execution_count": null, "id": "76d1a387", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"morning star\"] != 0]" ] }, { "cell_type": "markdown", "id": "3db4e86f", "metadata": {}, "source": [ "And here we can see all the data with evening star detected." ] }, { "cell_type": "code", "execution_count": null, "id": "558eed33", "metadata": {}, "outputs": [], "source": [ "data_csv[data_csv[\"evening star\"] != 0]" ] }, { "cell_type": "markdown", "id": "36204bd6", "metadata": {}, "source": [ "And now we can see the specific part of the chart close to the time when the trading signals were to buy or to sell." ] }, { "cell_type": "markdown", "id": "0b370a5b", "metadata": {}, "source": [ "In the cell below you might change the time period you want to see precisely, buy you don't have to." ] }, { "cell_type": "code", "execution_count": null, "id": "753a92fb", "metadata": {}, "outputs": [], "source": [ "start_time = '1722-02-02 9:30:00' #change the start date to see the part that's interesting for you\n", "end_time = '2222-02-02 16:00:00' #change the end date to see the part that's interesting for you" ] }, { "cell_type": "code", "execution_count": null, "id": "55f43dc9", "metadata": {}, "outputs": [], "source": [ "ti_plot = [mpf.make_addplot(data_csv['rsi'][start_time:end_time], color='black', width=0.5, panel=1, ylabel=rsi_l),\n", " mpf.make_addplot(data_csv['upperbb'][start_time:end_time],color='b', title=bb_title, width=0.5), \n", " mpf.make_addplot(data_csv['lowerbb'][start_time:end_time],color='b', width=0.5)]\n", "mpf.plot(data_csv[start_time:end_time], figratio=(2,1), type='candle', \n", " title='data from {} to {} \\n with RSI (black) and Bollinger Bands (blue)'.format(start_time, end_time), \n", " tight_layout=True, style='yahoo',addplot=ti_plot)" ] }, { "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", "Meanwhile, you can try adding some other technical indicators to the bot from TA-lib. You will be probably able to calculate them with one line of code and then add anoter 'and' condition to your 'if' statements.\n", "\n", "And if you want, you can search Alpaca site for great videos regarding trading or you could wait for me to create another tutorial. I will do it, just don't know when, so you might want to subscribe to my YouTube channel and hit that bell button to get the notification. 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 might be 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", "- http://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 third 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\n" ] } ], "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 }