Python Trading Bot w/ Thinkorswim
Description
- This automated trading bot utilitizes, TDameritrade API, TD Websocket API, Tradier API, Gmail API, Discord integration and MongoDB.
- It's intended to be a "plug & play" type of bot for newbies to get invovled with Algo trading.
- You, the user, will have to create the alert system so the bot can trade these signals.
How it works (in its simplest terms)
- There are many ways to run this bot:
- BROKER
- You can specify your broker (TD or Tradier and can be changed via config.py)
- TD has a better paper trading set up, but Tradier is $10/mo subscription with free options trading
- ALERTS
- You can run it using Trey Thomas's way of using TD API to scan alerts (gmail alerts)
- You can run it scanning discord notifications
- TECHNICAL ANLYSIS
- You can filter these alerts (discord & gmail) to run a technical analysis before you trade it (can be turned off in config.py)
- Right now, this is just configured using Hull Moving Average & QQE (trending up/down while not being overbought/oversold)
- WEBSOCKET
- Open positions in Mongo will be picked up by the TD Websocket API (if turned on) and will continuously give you an update on pricing. Depending on the strategy, it might sell out of the position
- To assist in getting your websocket set up, I recommend watching this video: (Part Time Larry - TD Websocket) https://www.youtube.com/watch?v=P5YanfJFlNs
Thinkorswim
Thinkorswim can be used as a broker for LiveTrading or Papertrading OR it can just be used to send your bot buy signals. The Papertrading
feature of this bot WILL NOT show up in your papertrading account in ToS, but MongoDB can be set up to visually show your paper trading.
ToS is better than Traider with papertrading because Tradier data is DELAYED for 15m when not live trading.
- IN ORDER TO TRADE THINKORSWIM, IN CONFIG.PY --> RUN_TRADIER = False
- IN ORDER TO TRADE PAPERTRADER, IN CONFIG.PY --> RUN_LIVE_TRADER = FALSE
- If you want alerts coming from ThinkorSwim, you can set up an OptionHacker alert to send emails to an email you created.
- Create a scanner for your strategy. (Scanner name will have specific format needed)
- Set your scanner to send alerts to your non-personal gmail.
- When a symbol is populated into the scanner with "buy" or "buy_to_open", an alert is triggered and sent to gmail.
- If you're unfamilar with Trey's repo, please check this out. This is what the repo this code has been modeled after:
https://github.com/TreyThomas93/python-trading-bot-with-thinkorswim
Discord
- I personally use https://www.teklutrades.com/FlowAnalysis, I couldn't recommend his work enough.
- It's $40/month but the alerts are formatted well.
- If you have your own discord that you'd like to track alerts with, you'll have to format your own discord scanner and throw it in the discord folder.
You can replace discord_scanner.py with your own. If you don't have a discord_scanner, you can set RUN_DISCORD = False
Trading Bot (Python)
- Continuously scrapes email inbox/discord looking for alerts.
- Once found, bot will extract needed information and will run TA (if you have RUN_TA set to True in your config.py).
HOW TO SET UP config.py
- Create a copy of config.py.example and save it as config.py. Place it in the root of this folder (next to config.py.example)
- RUN_LIVE_TRADER: change to True if you want to trade using real money (True, False)
- RUN_TRADIER: change to True if you want to trade using Tradier as your broker (True, False) - the websocket is still ToS based,
so you will still need to have ToS set up if you'd like to runWebsocket with Tradier
- IS_TESTING: this might not work... it's intended to provide prices when the market is closed to make sure everything is still working in Mongo (True, False)
- TRADE_HEDGES: only changes the alerts from my discord_scanner (True, False). TekluTrades indicates when it thinks a flow might be a hedge. If you aren't using this flow, it won't affect your trading. If you are using this flow and would like to trade when there's a Hedge Alert flow, set to True
- MONGO_URI: your Mongo_URI (string) - (see Trey's repo if necessary)
- RUN_GMAIL & RUN_DISCORD: how do you want to get your alerts? Set to True for the alerts you'd like (True, False)
- PUSH_API_KEY: I use a free discord webhook to a personal discord channel, but this is possible too (string) [commented out]
- TIMEZONE: use a pytz timezone (string) https://gist.github.com/heyalexej/8bf688fd67d7199be4a1682b3eec7568
- RUN_TASKS: this runs on a totally separate python thread. I suggest having it set to True (True, False). Functions can be found in assets -> tasks.py
- RUN_WEBSOCKET: this will actively stream all open_posiitons in mongo with pricing and update it to mongo (True, False) - personal preference, I have it on
- TURN_ON_TIME, TURN_OFF_TRADES, SELL_ALL_POSITIONS, SHUTDOWN_TIME: ('HH:MM:SS')
- TURN_ON_TIME - not set up currently, when you'd like to start the mongo connection and boot up the bot
- TURN_OFF_TRADES - at what time would you like to stop accepting trades ('15:50:00') (ie. 10mins before close)
- SELL_ALL_POSITIONS - at what time would you like to start selling all positions?
- SHUTDOWN_TIME - at what time would you like to disconnect from Mongo? *MONGO CHARGES BASED OFF THE HOUR, NOT NUMBER OF CONNECTIONS, SO IT'S BEST TO SHUT THE CONNECTION ONCE MARKET IS CLOSED
TRADING CRITERIA
- MIN_OPTIONPRICE: (Float) as you're scanning discord alerts, what's the minimum option price you'd like to potentially trade?
- MAX_OPTIONPRICE: (Float) what's the maximum price you'd like to trade? NOTE: THE POSITION SIZE MUST BE SET IN MONGO IN STRATEGIES
- MIN_VOLUME: (Float) as you get alerts, python will send a request to TD API to get the price & volume of the option. What's the minimum volume you want to trade? You want to ensure that the contract is liquid
- MIN_DELTA: (Float) what's the minimum delta you'd like to trade? It takes absolute value
- GET_PRICE_FROM_TD: personal preference. Do you want to get your price quotes from TD or Tradier? Tradier is delayed for papertrading, so I always get price data from TD (True, False)
- BUY_PRICE: do you want to buy on the bidPrice, askPrice, lastPrice, mark? (string) I prefer to buy on bid, sell on ask
- SELL_PRICE: do you want to sell on the bidPrice, askPrice, lastPrice, mark? (string)
- MAX_QUEUE_LENGTH: (Float) a tasks has a KillQueueOrder function. If an order is queued for longer than MAX_QUEUE_LENGTH, then it's cancelled
- TAKE_PROFIT_PERCENTAGE: (Float) If trading OCO or Custom, what would you like your Take_Profit set as?
- Entry_Price * (1+Take_Profit_Percentage) = Take_Profit_Price
- STOP_LOSS_PERCENTAGE: (Float) If trading OCO or Custom, what would you like your Stop_Loss set as?
- Entry_Price * (1-Stop_Loss_Percentage) = Stop_Loss_Price
- TRAIL_STOP_PERCENTAGE: (Float) If trading Trail, it will set your trailing stop.
- Entry_Price * TRAIL_STOP_PERCENTAGE = Trail_Stop_Value
- Entry_Price - Trail_Stop_Value = Trail_Stop_Price
- Please note that this trail stop is not 100% developed yet (Traider does NOT have a trailing stop order, so the websocket will constantly update a closing order)
- RUNNER_FACTOR: (Float) this is currently not being used. It used to enter a new order of RUNNER_FACTOR * position_size after an order hits take_profit_price
- TRADE_MULTI_STRIKES: do you want to trade multiple strikes of the same options? TD sometimes sends gmails of 3 separate strikes in one email. If you don't want to trade these, it should be False (True, False)
TECHNICAL ANALYSIS
- RUN_TA: runs QQE & HULL_MOVING_AVG for alerts in the 10m timeframe (True, False)
- RUN_30M_TA: Runs QQE & HULL_MOVING_AVG for alerts in the 30m timeframe (True, False)
- If both are true, then the 10m and 30m timeframes are in agreement with your indicators (puts & calls have separate criteria)
TD STREAMING
- API_KEY, REDIRECT_URI, TOKEN_PATH: (string) please watch Part Time Larry's video to get a separate API_KEY for your account. You will need to create a new TD Developer account for this
- ACCOUNT_ID: (float) This is the TD account you'd like to trade with
- HEARTBEAT_SETTING: (float) in seconds, how often would you like a "heartbeat signal" from the websocket task incase you don't have any open positions, it will show that your streamer is still working
- STREAMPRICE_LINK: (string) in streamprice.py, would you like to trade out of a signal based on 'bid', 'ask', 'last'? I prefer ask price
DISCORD
- CHANNELID: (string) channel ID for discord alert channel
- DISCORD_AUTH: (string) authorization for discord alert channel
- DISCORD_USER: (string) username of discord alert bot
- STRATEGY: (string) what do you want to name your strategy coming from discord alerts
-
DISCORD_WEBHOOK: (string) personal discord webhook (this is how I get alerts instead of Pushsafer)
TRADIER
-
Tradier API is very easy to work with. It just needs the specific Access Token & Account Number to trade via Paper or Live
- LIVE_ACCESS_TOKEN: (string) this will be your live trading access-token from Tradier website
- LIVE_ACCOUNT_NUMBER: (string) this will be your live trading account-number from Tradier
- SANDBOX_ACCESS_TOKEN: (string) on Tradier website (this is papertrading) - 15m delayed
- SANDBOX_ACCOUNT_NUMBER: (string) on Tradier website (this is papetrading) - 15m delayed
BACKTESTER
-
This is how to backtest your strategy - it's currently a work in progress
- POLYGON_URI: (string) - sign up for Polygon to get free PolygonAPI access - we use it for option price history (only 5 API requests per min for a free account, so it sleeps every 14 mins when grabbing a dataframe)
- EXT_DIR: (string) this is the root folder of your code (where main.py is located)
- LOOKBACK_DAYS: (float) amount of day lookback from current UCT time to backtest (max discord alerts is 50 alerts due to json requests)
- TEST_DISCORD: (True, False) if you'd like to backtest discord alerts, set to True
- TEST_CLOSED_POSITIONS: (True, False) if you'd like to backtest mongo closed_positions, set to True
- POSITION_SIZE: (float) what's your assumed position size for each trade? If option entry_price exceeds position size, it doesn't trade
-
ATTENTION - The bot is designed to either paper trade or live trade, but not at the same time. You can do one or the other. This can be changed by: TD --> the value set for the "Account_Position" field located in your account object stored in the users collection in mongo. The options for this field are "Paper" and "Live". These are case sensitive. By default when the account is created, it is set to "Paper" as a safety precaution for the user. TRADIER --> switch your RUN_LIVE_TRADER in config.py to True
Getting Started
DEPENDENCIES
[dev-packages]
[packages]
- google-api-python-client = "*"
- google-auth-httplib2 = "*"
- google-auth-oauthlib = "*"
- python-dotenv = "*"
- pymongo = "*"
- dnspython = "*"
- requests = "*"
- pytz = "*"
- psutil = "*"
- certifi = "*"
- polygon = "*"
- pyti = "*"
- pandas = "*"
- pandas_ta = "*"
[venv]
[requires]
THINKORSWIM
- Create a strategy that you want to use in the bot.
-
Create a scanner and name it using the format below:
- REVA is the strategy name example.
- BUY is the side. Can be BUY, BUY_TO_OPEN, BUY_TO_CLOSE, SELL, SELL_TO_CLOSE, SELL_TO_OPEN
- ATTENTION - Your scanner names must have the same strategy names for the buy and sell scanners, or the bot will not be able to trade correctly.
-
Example:
- MyRSIStrategy, BUY
- MyRSIStrategy, SELL
-
You will need to offset the scanner logic to prevent premature alerts from firing. This is due to the fact of the current candle constantly repainting and meeting/not meeting criteria.
- This is how an entry strategy in the charts may look.
-
Set up the alert for the scanner. View images below:
-
Set Event dropdown to "A symbol is added"
-
Check the box that says "Send an e-mail to all specified e-mail addresses"
-
Check the radio button thats says "A message for every change"
- You should now start to receive alerts to your specified gmail account.
TDAMERITRADE API TOKENS
- You will need an access token and refresh token for each account you wish to use.
- This will allow you to connect to your TDA account through the API.
- Here is Trey Thomas's repo to help you to get these tokens and save them to your mongo database, in your users collection.
GMAIL
-
First off, it is best to create an additional and seperate Gmail account and not your personal account.
-
Make sure that you are in the account that will be used to receive alerts from Thinkorswim.
-
Step by Step (Follow this to setup Gmail API):
- https://developers.google.com/gmail/api/quickstart/python
- https://developers.google.com/workspace/guides/create-project
- https://developers.google.com/workspace/guides/create-credentials
- After you obtain your credentials file, make sure you rename it to credentials.json and store it in the creds folding within the gmail package in the program.
- Run the program and you will go through the OAuth process. Once complete, a token.json file will be stored in your creds folder.
- If you get an access_denied during the OAuth process, try this: https://stackoverflow.com/questions/65184355/error-403-access-denied-from-google-authentication-web-api-despite-google-acc
MONGODB
-
Create a MongoDB account, create a cluster, and create one database with the following names:
- Api_Trader
-
The Api_Trader will contain all live and paper data. Each document contains a field called Account_Position which will tell the bot if its for paper trading or live trading.
-
You will need the mongo URI to be able to connect pymongo in the program. Store this URI in a config.env file within your mongo package in your code.
ApiTrader
- The collections you will find in the Api_Trader database will be the following:
- analysis (THIS IS WHERE THE DISCORD ALERTS GO AFTER THEY'RE SCANNED TO AVOID RE-TRADING)
- users
- queue
- open_positions
- closed_positions
- rejected
- canceled
- strategies
-
The analysis collection stores all alerts so that the bot recognizes a duplicate alert (tracks timestamp of alert & symbol)
-
The users collection stores all users and their individial data, such as name and accounts.
-
The queue collection stores non-filled orders that are working or queued, until either cancelled or filled.
-
The open_positions collection stores all open positions and is used to help determine if an order is warranted.
-
The closed_positions collection stores all closed positions after a trade has completed.
-
The rejected collection stores all rejected orders.
-
The canceled collection stores all canceled orders.
-
The strategies collection stores all strategies that have been used with the bot. Here is an example of a strategy object stored in mongo: {"Active": True, "Order_Type": "STANDARD", "Asset_Type": asset_type, "Position_Size": 500, "Position_Type": "LONG", "Trader": self.user["Name"], "Strategy": strategy, }
-
FYI - You are able to add more collections for additional tasks that you so wish to use with the bot. Mongo will automatically add a collection if it doesnt exist when the bot needs to use it so you dont need to manually create it.
PUSHSAFER
-
Pushsafer IS integrated, but personally, I use a discord webhook to a personal server since it's a free service
-
If you choose to use it, Pushsafer allows you to send and receive push notifications to your phone from the program.
-
This is handy for knowing in real time when trades are placed.
-
The first thing you will need to do is register:
https://www.pushsafer.com/
-
Once registered, read the docs on how to register and connect to devices. There is an Android and IOS app for this.
-
You will also need to pay for API calls, which is about $1 for 1,000 calls.
-
You will also need to store your api key in your code in a config.py file.
DISCREPENCIES
-
This program is not perfect. I am not liable for any profits or losses.
-
There are several factors that could play into the program not working correctly. Some examples below:
- TDAmeritrades API is buggy at times, and you may lose connection, or not get correct responses after making requests.
- Thinkorswim scanners update every 3-5 minutes, and sometimes symbols wont populate at a timely rate. I've seen some to where it took 20-30 minutes to finally send an alert.
- Gmail servers could go down aswell. That has happened in the past, but not very common.
- MongoDB can go down at times and the bot may do unexpected things.
- Discord notifications are often spotty, so the alerts may not come in at times either.
- And depending on who you have hosting your server for the program, that is also subject to go down sometimes, either for maintenance or for other reasons.
- As for refreshing the refresh token, I have been running into issues when renewing it. The TDA API site says the refresh token will expire after 90 days, but for some reason It won't allow you to always renew it and may give you an "invalid grant" error, so you may have to play around with it or even recreate everything using this repo. Just make sure you set it to existing user in the script so it can update your account.
- Lastly, running the websocket with TD as your broker, will need high supervision. The websocket executes the close orders, so if there's an error and the bot stops, the position will never be closed.
-
The program is very indirect, and lots of factors play into how well it performs. For the most part, it does a great job.
WHAT I USED AND COSTS
SERVER FOR HOSTING PROGRAM
- PythonAnywhere -- $7 / month
DATABASE
- MongoDB Atlas -- Approx. $25 / month.
- I currently use the M5 tier. You may be able to do the M2 tier. If you wont be using the web app then you don't need a higher level tier.
NOTIFICATION SYSTEM
- PushSafer -- Less than $5 / month
DISCORD NOTIFICATIONS
- TekluTrades -- $49 / month
FINAL THOUGHTS
-
I honestly cannot thank Trey enough for getting this base code started and his help along the way when I first got this started. I didn't know much Python when I started this journey in early 2021 so Trey's help was monumental. This is in continous development, with hopes to make this program as good as it can possibly get. I know this README might not do it justice with giving you all the information you may need, and you most likely will have questions. Therefore, don't hesitate to contact me on Discord below. As for you all, I would like your input on how to improve this. I appreciate all the support! Thanks, Matt.
-
DISCORD GROUP - Trey created a Discord group to allow for a more interactive enviroment that will allow for all of us to answer questions and talk about the program. I am the mod on there if you have any questions Discord Group
-
I'm currently working a backtest into this to backtest your entries from your closed positions
-
Also, If you like what I have to offer, please support me here!
Venmo - @Matt-Ogden