├── .env_example ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── app.py ├── app_launcher.bat ├── audio ├── order_placed.wav └── strategy_notified.wav ├── discord_bot ├── bot_js │ ├── .gitignore │ └── src │ │ └── index.js ├── bot_runner.py ├── discord_notify_bot.py └── discord_reply_bot.py ├── docs ├── docs_img │ ├── gui_0.png │ ├── gui_1.png │ ├── gui_2.png │ ├── gui_3.png │ ├── gui_4.png │ ├── gui_5.png │ ├── gui_6.png │ ├── gui_7.png │ ├── gui_8.png │ ├── gui_8_download.png │ ├── gui_9.png │ ├── icon.png │ ├── install_vc_1.png │ ├── install_vc_2.png │ ├── log_in_1.png │ ├── log_in_setup_1.png │ ├── log_in_setup_2.png │ ├── log_in_setup_3.png │ ├── log_in_setup_4.png │ ├── log_in_setup_5.png │ ├── strategy_1.png │ └── strategy_2.png ├── first_run_setup.md └── fix_install_error.md ├── gui ├── UI Preview │ ├── 0_DashboardLogin.png │ ├── 1_DashboardLogged.png │ ├── 2_StrategyMonitor.png │ ├── 3_TraderProfile.png │ ├── 4_TradingList.png │ ├── 5_Performance.png │ ├── 6_APPLog.png │ ├── 7_Message.png │ ├── 8_DownloadData.png │ └── 9_SaveExit.png ├── __init__.py ├── gui_0_DashboardLogin │ ├── DashboardLogin.py │ ├── __init__.py │ └── frame0 │ │ ├── entry_1.png │ │ ├── entry_2.png │ │ ├── entry_3.png │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── gui_1_DashboardLogged │ ├── DashboardLogged.py │ ├── __init__.py │ └── frame1 │ │ ├── entry_1.png │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── gui_2_StrategyMonitor │ ├── StrategyMonitor.py │ ├── __init__.py │ └── frame2 │ │ ├── entry_1.png │ │ ├── entry_2.png │ │ ├── entry_3.png │ │ ├── entry_4.png │ │ ├── entry_5.png │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── gui_3_TreaderProfile │ ├── TreaderProfile.py │ ├── __init__.py │ └── frame3 │ │ ├── entry_1.png │ │ ├── entry_2.png │ │ ├── entry_3.png │ │ ├── entry_4.png │ │ ├── entry_5.png │ │ ├── image_1.png │ │ ├── image_10.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ ├── image_8.png │ │ └── image_9.png ├── gui_4_TradingList │ ├── TradingList.py │ ├── __init__.py │ └── frame4 │ │ ├── entry_1.png │ │ ├── entry_2.png │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── gui_5_Performance │ ├── Performance.py │ ├── __init__.py │ └── frame5 │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── gui_6_APPLog │ ├── APPLog.py │ ├── __init__.py │ └── frame6 │ │ ├── entry_1.png │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── gui_7_Message │ ├── Message.py │ ├── __init__.py │ └── frame7 │ │ ├── entry_1.png │ │ ├── entry_2.png │ │ ├── entry_3.png │ │ ├── entry_4.png │ │ ├── entry_5.png │ │ ├── image_1.png │ │ ├── image_10.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ ├── image_8.png │ │ └── image_9.png ├── gui_8_DownloadData │ ├── DownloadData.py │ ├── __init__.py │ └── frame8 │ │ ├── entry_1.png │ │ ├── entry_2.png │ │ ├── entry_3.png │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── gui_9_SaveExit │ ├── SaveExit.py │ ├── __init__.py │ └── frame9 │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── image_8.png ├── icon.ico ├── icon.png ├── trading.ico └── trading.png ├── image ├── bkg_tmp1.jpg └── bkg_tmp2.jpg ├── quoter ├── Quoter.py ├── __init__.py ├── quoter_Webull.py └── quoter_Yahoo.py ├── requirements.txt ├── server └── __init__.py ├── strategy ├── My_Strategy.py ├── Strategy.py └── __init__.py ├── trader ├── Trader.py └── __init__.py └── utils ├── SQLiteHelper.py ├── dataIO.py ├── download_max_history_candles.py ├── format_str.py ├── input_check.py ├── play_sound.py ├── send_email.py ├── send_text_message.py └── update_intraday_data_history.py /.env_example: -------------------------------------------------------------------------------- 1 | DISCORD_NOTIFY_TOKEN=NzxxxxxxxxxxJ9.xxxXXXXx.xxxxxxxxxxxxxx_xxxxxx 2 | CHANNEL_ID_GENERAL=123456789012345678 3 | CHANNEL_ID_TRANSACTION=234567890123456789 4 | CHANNEL_ID_STRATEGY=396715658140322089 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: LukeWang01 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | *.bin 3 | webull_credentials.json 4 | *.json 5 | .idea/ 6 | *.ipynb 7 | *.pyc 8 | *.log 9 | **/__pycache__/ 10 | utils/tmp_utils.py 11 | quoter/quoter_test.py 12 | utils/testutils.py 13 | .vscode/ 14 | *.db 15 | app_testing.py 16 | *.csv 17 | audio/tmp_sound.py 18 | .DS_Store 19 | strategy/Luke_Strategy.py 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zilu Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

logo Program Trading Based on Webull

6 |

Market data from Webull and Yahoo! Build your own strategy, and let the program trade for you with your Webull account.

7 | 8 |
9 | 10 | ## ⚠️ 0. Disclaimer and Security 11 | - This App is not affiliated with [Webull Financial LLC](https://www.webull.com/). 12 | - The Webull API used in this App is from: 13 | - https://pypi.org/project/webull/ (Pypi.org) 14 | - https://github.com/tedchou12/webull (Github, by tedchou12) 15 | - Simply speaking, this API is like simulating you are using the Webull web platform. 16 | - The official [Webull API](https://github.com/webull-inc/openapi-python-sdk) is still under testing. This App will switch to the official API when it is ready. 17 | - The App GUI is developed via [Tkinter-Designer](https://github.com/ParthJadhav/Tkinter-Designer), an easy and fast way to create a Python GUI. 18 | - Feel free to **fork** and **edit** the code to customize the App for your own use. 19 | 20 |
21 | 22 | - For security: 23 | - Do **NOT** save your password and PID locally. Type it when you log in. 24 | - Do **NOT** upload or share the credential JSON files generated by the App. 25 | - The `access_token` will expire **weekly** for your account security, you need to re-login to set up a new one. 26 | 27 |
28 | 29 | - If you don't have Webull account, feel free to use my [referral link](https://www.webull.com/ko-yield/1686282581612-6819bd?__app_cfg__=%7B%22supportTheme%22%3Atrue%7D&inviteCode=vxXUIqoQXd1E&source=hdx) to sign up, you can get 12 free stocks after depositing $0.01 or more. 30 | 31 | - Give repository a star if it helps~ 32 | 33 |
34 | 35 | 36 | #### PS, Discord Server for program trading: 37 | https://discord.gg/9uUpjyyqkZ 38 | 39 | or, just add me on Discord. My Discord ID: squawkwallstreet 40 | 41 |
42 | 43 | 44 | ## 🤖 1. Install 45 | 46 | ### 🐍 1.1 Install Python 47 | 48 | Use the link below to download and install Python. (Make sure to add Python to your system PATH during the installation) 49 | 50 | https://www.python.org/downloads/ 51 | 52 | ### 💾 1.2 Clone this repository 53 | ``` 54 | git clone https://github.com/LukeWang01/Program-Trading-Based-on-Webull.git 55 | ``` 56 | 57 | or, 58 | 59 | Download the Zip file and unzip to a folder. 60 | 61 | ### 💽 1.3 Install the required packages: 62 | 63 | ``` 64 | pip install -r requirements.txt 65 | ``` 66 | 67 | or, 68 | 69 | ``` 70 | pip3 install -r requirements.txt 71 | ``` 72 | 73 | If got errors when installing (click here) 74 | 75 | ### 🔐 1.4 Environment Variables 76 | 77 | **Create a `.env` File:** 78 | - Copy the contents from `.env_example` to a new file named `.env` in the root directory of your project. 79 | - This file will store sensitive configuration details such as your Discord bot token and channel IDs. 80 | 81 | **Create and Configure Your Discord Bot:** 82 | - Visit the [Discord Developer Portal](https://discord.com/developers/docs/intro) to create a new bot. You will need to register a new application and add a bot to that application. 83 | - After creating your bot, you will find the bot token in the Bot section. Make sure to save this token in your `.env` file under `DISCORD_NOTIFY_TOKEN`. 84 | 85 | **Enable Developer Mode in Discord:** 86 | - On desktop: Discord GUI: Access User Settings -> go to Advanced -> enable Developer Mode. 87 | - With Developer Mode enabled, you can right-click on any channel in your server to copy its ID. Use these IDs as needed in your `.env` file for `CHANNEL_ID_GENERAL`, `CHANNEL_ID_TRANSACTION`, etc. 88 | 89 |
90 | 91 | ## 🚀 2. Run 92 | 93 | ### 2.1 Launch the App 94 | 95 | Go to the Program-Trading-Based-on-Webull folder, open the terminal, 96 | 97 | ``` 98 | python app.py 99 | ``` 100 | 101 | or, 102 | 103 | Double-click the `app_launcher.bat` to run. (Windows only) 104 | 105 |
106 | 107 | log in 108 | 109 |
110 | 111 |
112 | 113 | ### 2.2 First-time launch setup (Setup only once for the Authentication) 114 | 115 | Instructions for the first run setup (click here) 116 | 117 |
118 | 119 | ## 💲 3. Build your own strategy 120 | 121 | You can create your strategy following the example strategy: 122 | 123 | `Program-Trading-Based-on-Webull/strategy/My_Strategy.py` (Click here to open) 124 | 125 |
126 | 127 | Just override the `strategy_decision()` function in the `My_Strategy` class, and add any attributes you need. 128 | 129 | 130 |
131 | 132 | Run your strategy and make tradings after logging in: 133 | 134 |
135 | 136 | strategy_1 137 | 138 | strategy_1 139 | 140 |
141 | 142 | ## 🖥️ 4. App Features 143 | 144 | #### 4.1 Log in 145 | strategy_1 146 | 147 | #### 4.2 Account Dashboard 148 | strategy_1 149 | 150 | #### 4.3 Select and run strategy, monitor the market and strategy 151 | strategy_1 152 | 153 | #### 4.4 Edit your profile settings, add notification email account 154 | strategy_1 155 | 156 | #### 4.5 View the trading orders and transaction history 157 | strategy_1 158 | 159 | #### 4.6 View the performance of your strategy or account 160 | strategy_1 161 | 162 | #### 4.7 View the App running log 163 | strategy_1 164 | 165 | #### 4.8 Edit the notification email account settings 166 | strategy_1 167 | 168 | #### 4.9 Download the stock intraday and history data, saving as csv file 169 | strategy_1 170 | 171 | strategy_1 172 | 173 | #### 4.10 Safe exit the App 174 | strategy_1 175 | 176 |
177 | 178 |
179 | 180 |
181 | 182 | #### Feel free to edit the code to customize the App for your own use. 183 | 184 |
185 | 186 | **Good luck to all traders!** 187 | 188 | Luke 189 | 190 |
191 | 192 | ## 🦋 Supporting Me 193 | 194 |
195 | 196 | Buy Me A Coffee 197 | Paypal 198 |
199 | 200 | 201 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/__init__.py -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | """ 2 | APP GUI Main interface 3 | LukeLab, 05/2023 4 | """ 5 | import threading 6 | import time 7 | import tkinter as tk 8 | 9 | from tkinter import messagebox 10 | from tkinter import font as tk_font 11 | 12 | import schedule 13 | 14 | import quoter.Quoter as Quoter 15 | 16 | from trader.Trader import Trader 17 | from gui.gui_0_DashboardLogin.DashboardLogin import DashboardLogin 18 | from gui.gui_1_DashboardLogged.DashboardLogged import DashboardLogged 19 | from gui.gui_2_StrategyMonitor.StrategyMonitor import StrategyMonitor 20 | from gui.gui_3_TreaderProfile.TreaderProfile import TreaderProfile 21 | from gui.gui_4_TradingList.TradingList import TradingList 22 | from gui.gui_5_Performance.Performance import Performance 23 | from gui.gui_6_APPLog.APPLog import APPLog 24 | from gui.gui_7_Message.Message import Message 25 | from gui.gui_8_DownloadData.DownloadData import DownloadData 26 | from gui.gui_9_SaveExit.SaveExit import SaveExit 27 | from utils.SQLiteHelper import SQLiteHelper 28 | from utils.dataIO import logging_info 29 | 30 | 31 | class TradingApp(tk.Tk): 32 | def __init__(self, *args, **kwargs): 33 | # init the main window 34 | tk.Tk.__init__(self, *args, **kwargs) 35 | self.name = "TradingApp" 36 | 37 | # set app icon 38 | self.iconbitmap("gui/icon.ico") 39 | 40 | # set app window size 41 | self.geometry("1096x728+200+100") 42 | self.configure(bg="#FFFFFF") 43 | self.title("Program Trading Based on Webull") 44 | self.title_font = tk_font.Font(family="Arial Rounded MT Bold", size=18, weight="bold", slant="italic") 45 | self.resizable(False, False) 46 | 47 | # set frame dict 48 | self.frames = {} 49 | 50 | # init frames 51 | for F in (DashboardLogin, DashboardLogged, StrategyMonitor, TreaderProfile, TradingList, 52 | Performance, APPLog, Message, DownloadData, SaveExit): 53 | page_name = F.__name__ 54 | frame = F(self) 55 | self.frames[page_name] = frame 56 | frame.grid(row=0, column=0, sticky="nsew") 57 | self.bind("", self.window_clicked) # bind window click event 58 | logging_info("GUI frames initialized.") 59 | 60 | # Main window state: 61 | self.cnt = 0 62 | self.event_x = -1 63 | self.event_y = -1 64 | self.current_frame = self.frames["DashboardLogin"] 65 | 66 | # Trading State: 67 | self.trader = Trader() 68 | self.logged_in = False 69 | 70 | # Email Notification State: 71 | self.sender_email = "" 72 | self.sender_password = "" 73 | self.receiver_email_1 = "" 74 | self.receiver_email_2_bcc = "" 75 | self.enable_email_notify = 1 76 | 77 | # Trader Profile State: 78 | self.save_user_email = 1 79 | self.PID_timeout = 15 80 | 81 | # Database State: 82 | self.db_connected = False 83 | self.db_name = "tradingApp.db" 84 | self.db = SQLiteHelper(self.db_name) 85 | self.read_trader_info() 86 | self.read_email_notification_state() 87 | self.read_trader_profile_state() 88 | 89 | # show first frame 90 | self.show_frame("DashboardLogin") 91 | 92 | # App variables: 93 | # 1. set real-time Index Price: 94 | self.spx_price = 0 95 | self.dji_price = 0 96 | self.ixic_price = 0 97 | self.update_market_price(Quoter.get_market_index_real_time_price()) 98 | 99 | # Frontend functions: 100 | def window_clicked(self, event): 101 | self.event_x = event.x 102 | self.event_y = event.y 103 | 104 | def top_bar_clicked(self, x, y): 105 | # Not core funtion of the app, implement later 106 | pass 107 | 108 | def sidebar_clicked(self, x, y): 109 | if 97 <= y <= 147: 110 | if self.logged_in: 111 | # check login status, or using webull api to do hard check 112 | self.show_frame("DashboardLogged") 113 | else: 114 | self.show_frame("DashboardLogin") 115 | elif 161 <= y <= 211: 116 | self.show_frame("StrategyMonitor") 117 | elif 225 <= y <= 275: 118 | if self.logged_in: 119 | self.show_frame("TreaderProfile") 120 | else: 121 | messagebox.showinfo("Oops", "Sensitive Info, Please login first!") 122 | elif 289 <= y <= 339: 123 | if self.logged_in: 124 | self.show_info_message("Fetching trading list...") 125 | self.show_frame("TradingList") 126 | else: 127 | messagebox.showinfo("Oops", "Sensitive Info, Please login first!") 128 | elif 353 <= y <= 403: 129 | if self.logged_in: 130 | self.show_frame("Performance") 131 | else: 132 | messagebox.showinfo("Oops", "Sensitive Info, Please login first!") 133 | elif 417 <= y <= 467: 134 | self.show_frame("APPLog") 135 | elif 481 <= y <= 531: 136 | self.show_frame("Message") 137 | elif 545 <= y <= 595: 138 | self.show_frame("DownloadData") 139 | elif 609 <= y <= 659: 140 | result = messagebox.askyesno("Confirmation", "Do you want to exit?") 141 | if result: 142 | self.show_frame("SaveExit") 143 | self.save_app_state() 144 | else: 145 | pass 146 | 147 | def show_frame(self, page_name): 148 | frame = self.frames[page_name] 149 | self.current_frame = frame 150 | frame.tkraise() 151 | frame.update_data() 152 | 153 | # Back-end functions: 154 | # Trader: 155 | def read_trader_info(self): 156 | self.trader.set_device_name(self.db.get_device_name()) 157 | self.trader.set_auth_access_token(self.db.get_access_token()) 158 | self.trader.set_auth_did(self.db.get_did()) 159 | self.trader.set_auth_uuid(self.db.get_uuid()) 160 | self.trader.set_user_name(self.db.get_email()) 161 | 162 | # DashboardLogin 163 | def setup_did(self, did): 164 | self.trader.set_auth_did(did) 165 | self.db.update_did(did) 166 | logging_info('set did successfully') 167 | 168 | def setup_uuid(self, uuid): 169 | self.trader.set_auth_uuid(uuid) 170 | self.db.update_uuid(uuid) 171 | logging_info('set uuid successfully') 172 | 173 | def set_access_token(self, access_token): 174 | self.trader.set_auth_access_token(access_token) 175 | self.db.update_access_token(access_token) 176 | logging_info('set access token successfully') 177 | 178 | def set_device_name(self, device_name): 179 | self.trader.set_device_name(device_name) 180 | self.db.update_device_name(device_name) 181 | logging_info('set device name successfully') 182 | 183 | def login(self, email, password, pid): 184 | # print(f"email: {email}, parent login called") 185 | self.db.update_email(email) 186 | self.trader.set_trader_info(email, password, pid) 187 | if self.trader.login_preparation(): 188 | res = self.trader.log_in() 189 | if res: 190 | self.logged_in = True 191 | self.show_frame("DashboardLogged") 192 | logging_info(f"User: {email} logged in successfully.") 193 | else: 194 | messagebox.showerror("Oops something went wrong", "please check username or password. ") 195 | logging_info(f"User: {email} failed to log in.") 196 | else: 197 | messagebox.showerror("Oops something went wrong", "Please setup UUID/DID/ACCESS_TOKEN. ") 198 | 199 | def save_app_state(self): 200 | pass 201 | 202 | def set_email_notification_state(self, sender_email, sender_password, receiver_email_1, receiver_email_2_bcc, 203 | enable_email_notify): 204 | self.sender_email = sender_email 205 | self.sender_password = sender_password 206 | self.receiver_email_1 = receiver_email_1 207 | self.receiver_email_2_bcc = receiver_email_2_bcc 208 | self.enable_email_notify = enable_email_notify 209 | 210 | self.db.update_sender_email(sender_email) 211 | self.db.update_sender_password(sender_password) 212 | self.db.update_receiver_email_1(receiver_email_1) 213 | self.db.update_receiver_email_2_bcc(receiver_email_2_bcc) 214 | self.db.update_enable_email_notify(enable_email_notify) 215 | logging_info("Email notification state update to DB") 216 | 217 | def read_email_notification_state(self): 218 | self.sender_email = self.db.get_sender_email() 219 | self.sender_password = self.db.get_sender_password() 220 | self.receiver_email_1 = self.db.get_receiver_email_1() 221 | self.receiver_email_2_bcc = self.db.get_receiver_email_2_bcc() 222 | self.enable_email_notify = self.db.get_enable_email_notify() 223 | 224 | def is_email_setup(self): 225 | if self.sender_email == "" or self.sender_password == "" or self.receiver_email_1 == "": 226 | return False 227 | else: 228 | return True 229 | 230 | def exit_app(self): 231 | self.show_info_message("Exiting...") 232 | logging_info("APP exited") 233 | # sys.exit() 234 | self.destroy() 235 | 236 | def show_info_message(self, message, duration=2000): 237 | # Create a Toplevel window for the info message 238 | info_window = tk.Toplevel() 239 | info_window.wm_overrideredirect(True) # Remove window decorations 240 | info_window.attributes("-topmost", True) # Keep the window on top 241 | info_window.configure(bg="#333333") # Set background color 242 | info_window.attributes("-alpha", 0.9) # Set transparency 243 | 244 | # Create a Label widget to display the message 245 | label = tk.Label(info_window, text=message, fg="white", bg="#333333", padx=10, pady=5) 246 | label.pack() 247 | 248 | # Calculate the position of the info window to be centered within the main window 249 | root_x = self.winfo_rootx() + self.winfo_width() // 2 - info_window.winfo_reqwidth() // 2 + 100 250 | root_y = self.winfo_rooty() + self.winfo_height() // 2 - info_window.winfo_reqheight() // 2 + 300 251 | info_window.geometry(f"+{root_x}+{root_y}") 252 | 253 | # After the specified duration, close the info window 254 | info_window.after(duration, info_window.destroy) 255 | 256 | def update_market_price(self, latest_prices): 257 | self.spx_price = f'SPX: {round(latest_prices["^GSPC"], 2)}' 258 | self.dji_price = f'DJI: {round(latest_prices["^DJI"], 2)}' 259 | self.ixic_price = f'IXIC: {round(latest_prices["^IXIC"], 2)}' 260 | if self.current_frame.name != 'DashboardLogin': 261 | self.current_frame.update_market_status() 262 | 263 | def read_trader_profile_state(self): 264 | self.save_user_email = self.db.get_save_user_email() 265 | self.PID_timeout = self.db.get_PID_expired() 266 | if self.PID_timeout != self.trader.PID_timeout: 267 | self.trader.set_PID_expiry(self.PID_timeout) 268 | 269 | def set_trader_profile_state(self, sender_email, sender_password, receiver_email_1, save_user_email, pid_expire): 270 | # email notification: 271 | self.sender_email = sender_email 272 | self.sender_password = sender_password 273 | self.receiver_email_1 = receiver_email_1 274 | 275 | # trader profile: 276 | self.save_user_email = save_user_email 277 | self.PID_timeout = pid_expire 278 | self.trader.set_PID_expiry(pid_expire) 279 | 280 | # update database: 281 | self.db.update_sender_email(sender_email) 282 | self.db.update_sender_password(sender_password) 283 | self.db.update_receiver_email_1(receiver_email_1) 284 | self.db.update_save_user_email(save_user_email) 285 | self.db.update_PID_expired(pid_expire) 286 | logging_info('Trader profile update to DB.') 287 | 288 | 289 | # Global Functions: 290 | def bkg_scheduled_fun(app_obj): 291 | """ 292 | task can be scheduled later in the main thread 293 | """ 294 | # refresh market price every 30 seconds: 295 | schedule.every(30).seconds.do(get_real_time_idx_price, app_obj) 296 | logging_info('Background job scheduled.') 297 | # continuously run all scheduled background job, until stop flag is set: 298 | while not bkg_thread_stop_flag.is_set(): 299 | schedule.run_pending() 300 | time.sleep(1) 301 | 302 | 303 | def get_real_time_idx_price(app_obj): 304 | latest_prices = Quoter.get_market_index_real_time_price() 305 | app_obj.update_market_price(latest_prices) 306 | 307 | 308 | if __name__ == "__main__": 309 | # main thread: 310 | print('TradingApp starting...') 311 | app = TradingApp() 312 | logging_info('TradingApp started.') 313 | 314 | # new a thread to run background job: 315 | bkg_thread_stop_flag = threading.Event() 316 | bkg_thread = threading.Thread(target=bkg_scheduled_fun, args=(app,)) 317 | bkg_thread.start() 318 | app.mainloop() 319 | 320 | # closing App: 321 | bkg_thread_stop_flag.set() 322 | bkg_thread.join() 323 | logging_info('TradingApp closed, bkg_thread closed, App terminated successfully.') 324 | print('TradingApp closed, bkg_thread closed, App terminated successfully.') 325 | -------------------------------------------------------------------------------- /app_launcher.bat: -------------------------------------------------------------------------------- 1 | PowerShell.exe -Command "python .\app.py" 2 | -------------------------------------------------------------------------------- /audio/order_placed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/audio/order_placed.wav -------------------------------------------------------------------------------- /audio/strategy_notified.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/audio/strategy_notified.wav -------------------------------------------------------------------------------- /discord_bot/bot_js/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ -------------------------------------------------------------------------------- /discord_bot/bot_js/src/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const {Client, IntentsBitField} = require('discord.js'); 3 | 4 | const client = new Client({ 5 | intents: [ 6 | IntentsBitField.Flags.Guilds, 7 | IntentsBitField.Flags.GuildMessages, 8 | IntentsBitField.Flags.GuildMembers, 9 | IntentsBitField.Flags.MessageContent, 10 | ], 11 | }); 12 | 13 | client.on('ready', (c) => { 14 | console.log('Bot is ready'); 15 | console.log(`{c.user.tag} is online.`); 16 | }); 17 | 18 | client.on('messageCreate', (message) => { 19 | if (message.author.bot) { 20 | return; 21 | } 22 | 23 | if (message.content === 'ping') { 24 | message.channel.send('pong'); 25 | } 26 | }); 27 | 28 | 29 | client.login(process.env.TOKEN); 30 | -------------------------------------------------------------------------------- /discord_bot/bot_runner.py: -------------------------------------------------------------------------------- 1 | from discord_notify_bot import run_bot_send_msg, run_bot_send_msg_new_thread 2 | 3 | import json 4 | import hashlib 5 | import time 6 | 7 | from env._secrete import channel_id_transaction 8 | 9 | """ 10 | This script is used to send notification to discord channel when the trading_actions.json file is updated. 11 | Any new placed order will be sent to the discord channel. 12 | """ 13 | 14 | 15 | def read_json_file(file_path): 16 | with open(file_path, "r") as file: 17 | return json.load(file) 18 | 19 | 20 | def calculate_file_hash(file_path): 21 | hasher = hashlib.sha256() 22 | with open(file_path, "rb") as file: 23 | while True: 24 | data = file.read(65536) # Read in 64k chunks 25 | if not data: 26 | break 27 | hasher.update(data) 28 | return hasher.hexdigest() 29 | 30 | 31 | # JSON file path 32 | json_file_path = "../trader/trading_actions.json" 33 | 34 | # Record the initial state 35 | initial_json_state = read_json_file(json_file_path) 36 | print(initial_json_state[-1]) 37 | initial_json_hash = calculate_file_hash(json_file_path) 38 | print(initial_json_hash) 39 | 40 | 41 | while True: 42 | # Check if the file has changed 43 | current_json_hash = calculate_file_hash(json_file_path) 44 | 45 | if current_json_hash != initial_json_hash: 46 | print("JSON file has changed.") 47 | new_json_state = read_json_file(json_file_path) 48 | 49 | # Process the new JSON data as needed 50 | print("New JSON Data:", new_json_state[-1]) 51 | msg = f"-------------------------" 52 | msg += new_json_state[-1]["order_time"] + "\n" 53 | msg += f"account_id: {new_json_state[-1]['stock']} \n" 54 | msg += f"{new_json_state[-1]['order_direction']}: {new_json_state[-1]['stock_info']} \n" 55 | msg += f"Price: {new_json_state[-1]['order_price']} \n" 56 | msg += f"Qty: {new_json_state[-1]['order_qty']} \n" 57 | msg += f"Total Cost: {new_json_state[-1]['order_cost']} \n" 58 | msg += f"-------------------------" 59 | 60 | run_bot_send_msg_new_thread(msg, channel_id=channel_id_transaction) 61 | 62 | # Update the recorded state and hash 63 | initial_json_state = new_json_state 64 | initial_json_hash = current_json_hash 65 | 66 | # Sleep for a while (e.g., every 10 seconds) 67 | time.sleep(10) 68 | -------------------------------------------------------------------------------- /discord_bot/discord_notify_bot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import threading 3 | import time 4 | import discord 5 | from discord.ext import commands 6 | from env._secrete import discord_notify_Token, channel_id_general 7 | from dotenv import load_dotenv 8 | import os 9 | 10 | load_dotenv() 11 | discord_notify_Token = os.getenv('DISCORD_NOTIFY_TOKEN') 12 | channel_id_general = os.getenv('CHANNEL_ID_GENERAL') 13 | 14 | 15 | class MyBot(commands.Bot): 16 | def __init__(self, command_prefix, intents, message, channel_id): 17 | super().__init__(command_prefix=command_prefix, intents=intents) 18 | self.on_ready_message = message 19 | self.channel_id = channel_id 20 | 21 | @self.command() 22 | @commands.has_permissions(administrator=True) 23 | async def sync_command(ctx): 24 | await self.tree.sync() 25 | await ctx.send("syncing...") 26 | 27 | @self.hybrid_command() 28 | async def say_hi(ctx, name: str): 29 | await ctx.send(f"Hi {name}!") 30 | 31 | @self.hybrid_command() 32 | async def ping(ctx): 33 | await ctx.send("pong pong") 34 | 35 | async def on_ready(self): 36 | channel = self.get_channel(self.channel_id) 37 | if channel: 38 | await channel.send(self.on_ready_message) 39 | print(f"Message sent to channel {self.channel_id}") 40 | 41 | async def close_bot(self): 42 | await self.close() 43 | 44 | 45 | def send_notification(bot_obj): 46 | try: 47 | bot_obj.run(discord_notify_Token) 48 | # bot_obj.loop.run_forever() 49 | except Exception as e: 50 | pass 51 | loop = asyncio.new_event_loop() 52 | asyncio.set_event_loop(loop) 53 | loop.run_until_complete(bot_obj.close_bot()) 54 | loop.stop() # Stop the event loop 55 | 56 | 57 | def run_bot_send_msg_new_thread(msg, channel_id=channel_id_general): 58 | # print("start") 59 | command_prefix = "!" 60 | intents = discord.Intents.default() 61 | bot = MyBot( 62 | command_prefix=command_prefix, 63 | intents=intents, 64 | message=msg, 65 | channel_id=channel_id, 66 | ) 67 | 68 | thread_bot = threading.Thread(target=send_notification, args=(bot,)) 69 | thread_bot.start() 70 | # print('thread started') 71 | time.sleep(5) # wait for bot to send msg before stopping 72 | # print('thread stopping') 73 | bot.loop.call_soon_threadsafe(bot.loop.stop) # Stop the bot's event loop 74 | thread_bot.join() 75 | print("msg sent, thread joined") 76 | 77 | 78 | def run_bot_send_msg(msg, channel_id=channel_id_general): 79 | # print("start") 80 | command_prefix = "!" 81 | intents = discord.Intents.default() 82 | bot = MyBot( 83 | command_prefix=command_prefix, 84 | intents=intents, 85 | message=msg, 86 | channel_id=channel_id, 87 | ) 88 | 89 | bot.run(discord_notify_Token) 90 | bot.close_bot() 91 | print("done") 92 | 93 | 94 | if __name__ == "__main__": 95 | # if called by python in cmd 96 | import sys 97 | 98 | try: 99 | arg1 = sys.argv[1] 100 | run_bot_send_msg(arg1) 101 | except IndexError: 102 | arg1 = None 103 | print("no argument, please provide a message in cmd") 104 | -------------------------------------------------------------------------------- /discord_bot/discord_reply_bot.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from dotenv import load_dotenv 3 | import os 4 | 5 | load_dotenv() 6 | 7 | discord_notify_Token = os.getenv('DISCORD_NOTIFY_TOKEN') 8 | 9 | intents = discord.Intents.default() 10 | client = discord.Client(intents=intents) 11 | 12 | 13 | @client.event 14 | async def on_ready(): 15 | print('We have logged in as {0.user}'.format(client)) 16 | 17 | 18 | @client.event 19 | async def on_message(message): 20 | print(f'on_message: {message}') 21 | if message.author == client.user: 22 | print("self message") 23 | return 24 | 25 | if message.content.startswith('$hello'): 26 | await message.channel.send('Hello!') 27 | 28 | if message.content.startswith('$help'): 29 | await message.channel.send('Hello!') 30 | 31 | if message.content.startswith('$ping'): 32 | await message.channel.send('pong') 33 | 34 | if message.content.startswith('$sync'): 35 | await message.channel.send('sync') 36 | if message.content.startswith('wow'): 37 | await message.channel.send('hoh') 38 | 39 | 40 | client.run(discord_notify_Token) 41 | -------------------------------------------------------------------------------- /docs/docs_img/gui_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_0.png -------------------------------------------------------------------------------- /docs/docs_img/gui_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_1.png -------------------------------------------------------------------------------- /docs/docs_img/gui_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_2.png -------------------------------------------------------------------------------- /docs/docs_img/gui_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_3.png -------------------------------------------------------------------------------- /docs/docs_img/gui_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_4.png -------------------------------------------------------------------------------- /docs/docs_img/gui_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_5.png -------------------------------------------------------------------------------- /docs/docs_img/gui_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_6.png -------------------------------------------------------------------------------- /docs/docs_img/gui_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_7.png -------------------------------------------------------------------------------- /docs/docs_img/gui_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_8.png -------------------------------------------------------------------------------- /docs/docs_img/gui_8_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_8_download.png -------------------------------------------------------------------------------- /docs/docs_img/gui_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/gui_9.png -------------------------------------------------------------------------------- /docs/docs_img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/icon.png -------------------------------------------------------------------------------- /docs/docs_img/install_vc_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/install_vc_1.png -------------------------------------------------------------------------------- /docs/docs_img/install_vc_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/install_vc_2.png -------------------------------------------------------------------------------- /docs/docs_img/log_in_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/log_in_1.png -------------------------------------------------------------------------------- /docs/docs_img/log_in_setup_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/log_in_setup_1.png -------------------------------------------------------------------------------- /docs/docs_img/log_in_setup_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/log_in_setup_2.png -------------------------------------------------------------------------------- /docs/docs_img/log_in_setup_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/log_in_setup_3.png -------------------------------------------------------------------------------- /docs/docs_img/log_in_setup_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/log_in_setup_4.png -------------------------------------------------------------------------------- /docs/docs_img/log_in_setup_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/log_in_setup_5.png -------------------------------------------------------------------------------- /docs/docs_img/strategy_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/strategy_1.png -------------------------------------------------------------------------------- /docs/docs_img/strategy_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/docs/docs_img/strategy_2.png -------------------------------------------------------------------------------- /docs/first_run_setup.md: -------------------------------------------------------------------------------- 1 | ## First time launch setup (Setup only once for the Authentication 2 | 3 | ### 1. Log in to the [Webull official website](https://www.webull.com/) 4 | Then, go to the dev tools (press F12, or right click and select inspect): 5 | Now navigate to web trading page: 6 |
7 | 8 | ![5704e8798a0e1bcffb39208c781765e](docs_img/log_in_setup_1.png) 9 | 10 | ![image](https://github.com/LukeWang01/Program-Trading-Based-on-Webull/assets/31261577/a76b8b56-f762-4bd5-8431-d7d12ed10848) 11 | 12 |
13 | 14 | ### 2. Get the uuid, did, access_token from the dev tools window: 15 | 16 |
17 | 18 | ![723eb40b8c97b6c6fae9eb646f6ca38](docs_img/log_in_setup_3.png) 19 | ![68430ad2ad880cdfcac3e8e4165928c](docs_img/log_in_setup_4.png) 20 | 21 |
22 | 23 | Follow the instructions above, you can get the: 24 | 25 | `did`, `uuid`, `access_token` 26 | 27 | For `device name`, you can name it by yourself, such as `my_pc`. 28 | 29 | Then, setup these vaules before log in: 30 | 31 |
32 | 33 | ![caafc318aa48e9575021a848854c216](docs_img/log_in_setup_5.png) 34 | 35 |
36 | -------------------------------------------------------------------------------- /docs/fix_install_error.md: -------------------------------------------------------------------------------- 1 | ### 1. Error: Installing error for `simpleaduio` on Windows: 2 | 3 | If you got errors to install the `simpeaudio` library, you can try the following steps: 4 | 5 | 1. Go to https://visualstudio.microsoft.com/visual-cpp-build-tools/ 6 | 2. Download the C++ Build Tools Installer 7 | 3. Run the installer and install the C++ Build Tools 8 | 9 | After install, try: 10 | 11 | ``` 12 | pip install simpleaudio 13 | ``` 14 | 15 | or, 16 | 17 | ``` 18 | pip install -r requirements.txt 19 | ``` 20 | 21 | or, 22 | 23 | ``` 24 | pip3 install -r requirements.txt 25 | ``` 26 | 27 |
28 | 29 | ![5704e8798a0e1bcffb39208c781765e](docs_img/install_vc_1.png) 30 | 31 | ![5704e8798a0e1bcffb39208c781765e](docs_img/install_vc_2.png) 32 | 33 | 34 |
35 | 36 | ### 2. Error: Installing `lxml` error on MacOS: 37 | 38 | If you got the error when installing `lxml`: 39 | 40 | The error you're encountering indicates that the `lxml` library is having trouble building because it's missing some dependencies and the required developer tools. 41 | 42 | Here's a step-by-step guide to resolve this issue: 43 | 44 | 1. **Install Xcode Command Line Tools:** 45 | 46 | Open your terminal and run the following command to install Xcode Command Line Tools: 47 | 48 | ```bash 49 | xcode-select --install 50 | ``` 51 | 52 | Follow the on-screen prompts to complete the installation. 53 | 54 | 2. **Install Homebrew:** 55 | 56 | If you don't have Homebrew installed, you can install it using the following command: 57 | 58 | ```bash 59 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 60 | ``` 61 | 62 | 3. **Install Required Dependencies:** 63 | 64 | Use Homebrew to install the necessary dependencies for `lxml`: 65 | 66 | ```bash 67 | brew install libxml2 68 | brew install libxslt 69 | ``` 70 | 71 | 4. **Set Environment Variables:** 72 | 73 | Set the environment variables required for `lxml` to locate the dependencies correctly. Add these lines to your shell profile file (e.g., `~/.zshrc`): 74 | 75 | ```bash 76 | export CFLAGS="-I/usr/local/opt/libxml2/include -I/usr/local/opt/libxslt/include" 77 | export LDFLAGS="-L/usr/local/opt/libxml2/lib -L/usr/local/opt/libxslt/lib" 78 | ``` 79 | 80 | Then, run the following command to apply the changes: 81 | 82 | ```bash 83 | source ~/.zshrc 84 | ``` 85 | 86 | 5. **Retry Installation:** 87 | 88 | Now, try installing `lxml` again using `pip`: 89 | 90 | ```bash 91 | pip install lxml 92 | ``` 93 | 94 | With these steps, you should be able to successfully install `lxml`. 95 | 96 | After installation, try: 97 | 98 | `pip install simpleaudio` 99 | 100 | or, 101 | 102 | `pip install -r requirements.txt` 103 | 104 | or, 105 | 106 | `pip3 install -r requirements.txt` 107 | 108 |
109 | -------------------------------------------------------------------------------- /gui/UI Preview/0_DashboardLogin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/0_DashboardLogin.png -------------------------------------------------------------------------------- /gui/UI Preview/1_DashboardLogged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/1_DashboardLogged.png -------------------------------------------------------------------------------- /gui/UI Preview/2_StrategyMonitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/2_StrategyMonitor.png -------------------------------------------------------------------------------- /gui/UI Preview/3_TraderProfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/3_TraderProfile.png -------------------------------------------------------------------------------- /gui/UI Preview/4_TradingList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/4_TradingList.png -------------------------------------------------------------------------------- /gui/UI Preview/5_Performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/5_Performance.png -------------------------------------------------------------------------------- /gui/UI Preview/6_APPLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/6_APPLog.png -------------------------------------------------------------------------------- /gui/UI Preview/7_Message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/7_Message.png -------------------------------------------------------------------------------- /gui/UI Preview/8_DownloadData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/8_DownloadData.png -------------------------------------------------------------------------------- /gui/UI Preview/9_SaveExit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/UI Preview/9_SaveExit.png -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/__init__.py -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/DashboardLogin.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, Entry, PhotoImage, simpledialog, messagebox, END 4 | 5 | from utils.dataIO import logging_info 6 | from utils.input_check import is_valid_email, is_valid_phone_number, is_valid_pid 7 | 8 | import webbrowser 9 | 10 | 11 | class DashboardLogin(tk.Frame): 12 | def __init__(self, parent): 13 | # Initialize the frame 14 | tk.Frame.__init__(self, parent) 15 | self.name = "DashboardLogin" 16 | self.parent = parent 17 | self.current = False 18 | 19 | # UI elements: 20 | def relative_to_assets(path: str) -> Path: 21 | return ASSETS_PATH / Path(path) 22 | 23 | OUTPUT_PATH = Path(__file__).parent 24 | ASSETS_PATH = OUTPUT_PATH / Path("frame0") 25 | 26 | self.canvas = Canvas( 27 | self, 28 | bg="#3A59C7", 29 | height=728, 30 | width=1096, 31 | bd=0, 32 | highlightthickness=0, 33 | relief="ridge" 34 | ) 35 | self.canvas.place(x=0, y=0) 36 | 37 | # Load images 38 | self.image_image_1 = PhotoImage(file=relative_to_assets("image_1.png")) 39 | self.image_image_2 = PhotoImage(file=relative_to_assets("image_2.png")) 40 | self.image_image_3 = PhotoImage(file=relative_to_assets("image_3.png")) 41 | self.image_image_4 = PhotoImage(file=relative_to_assets("image_4.png")) 42 | self.entry_image_1 = PhotoImage(file=relative_to_assets("entry_1.png")) 43 | self.entry_image_2 = PhotoImage(file=relative_to_assets("entry_2.png")) 44 | self.entry_image_3 = PhotoImage(file=relative_to_assets("entry_3.png")) 45 | self.image_image_5 = PhotoImage(file=relative_to_assets("image_5.png")) 46 | self.image_image_6 = PhotoImage(file=relative_to_assets("image_6.png")) 47 | self.image_image_7 = PhotoImage(file=relative_to_assets("image_7.png")) 48 | self.image_image_8 = PhotoImage(file=relative_to_assets("image_8.png")) 49 | 50 | # Create canvas objects 51 | self.image_1 = self.canvas.create_image(548.0, 364.0, image=self.image_image_1) 52 | self.image_2 = self.canvas.create_image(645.0, 432.0, image=self.image_image_2) 53 | self.image_3_msg = self.canvas.create_image(1012.0, 30.0, image=self.image_image_3) 54 | self.image_4_notify = self.canvas.create_image(971.0, 30.0, image=self.image_image_4) 55 | self.entry_bg_1 = self.canvas.create_image(817.5, 491.5, image=self.entry_image_1) 56 | self.entry_bg_2 = self.canvas.create_image(817.5, 397.5, image=self.entry_image_2) 57 | self.entry_bg_3 = self.canvas.create_image(817.5, 307.0, image=self.entry_image_3) 58 | self.image_5 = self.canvas.create_image(328.0, 30.0, image=self.image_image_5) 59 | self.image_6 = self.canvas.create_image(534.0, 30.0, image=self.image_image_6) 60 | self.image_7 = self.canvas.create_image(534.0, 30.0, image=self.image_image_7) 61 | self.image_8 = self.canvas.create_image(328.0, 30.0, image=self.image_image_8) 62 | 63 | self.entry_3_email = Entry( 64 | self.canvas, 65 | bd=0, 66 | bg="#FFFFFF", 67 | fg="#000716", 68 | highlightthickness=0, 69 | font=("Arial Rounded MT Bold", 10) 70 | ) 71 | self.entry_3_email.place(x=688.0, y=287.0, width=259.0, height=38.0) 72 | 73 | self.entry_2_password = Entry( 74 | self.canvas, 75 | bd=0, 76 | bg="#FFFFFF", 77 | fg="#000716", 78 | highlightthickness=0, 79 | show="*" 80 | ) 81 | self.entry_2_password.place(x=688.0, y=380.0, width=259.0, height=33.0) 82 | 83 | self.entry_1_PID = Entry( 84 | self.canvas, 85 | bd=0, 86 | bg="#FFFFFF", 87 | fg="#000716", 88 | highlightthickness=0, 89 | show="*" 90 | ) 91 | self.entry_1_PID.place(x=688.0, y=474.0, width=259.0, height=33.0) 92 | 93 | self.canvas.pack(fill="both", expand=True) 94 | 95 | self.canvas.bind("", self.frame_clicked) 96 | self.canvas.bind("", self.login_clicked) 97 | 98 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 99 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 100 | self.entry_1_PID.bind("", lambda event: self.login_clicked()) 101 | 102 | def frame_clicked(self, event): 103 | x = event.x 104 | y = event.y 105 | if x <= 200: 106 | # Sidebar area clicked 107 | self.parent.sidebar_clicked(x, y) 108 | elif 200 <= x <= 1096 and y <= 60: 109 | # Top bar area clicked 110 | self.parent.top_bar_clicked(x, y) 111 | else: 112 | # frame area clicked 113 | if 712 <= x <= 936 and 529 <= y <= 564: 114 | self.login_clicked() 115 | elif 667 <= x <= 727 and 605 <= y <= 630: 116 | self.setup_did_clicked() 117 | elif 735 <= x <= 805 and 605 <= y <= 630: 118 | self.setup_uuid_clicked() 119 | elif 778 <= x <= 858 and 637 <= y <= 661: 120 | self.instruction_clicked() 121 | elif 814 <= x <= 894 and 605 <= y <= 630: 122 | self.access_token_clicked() 123 | elif 902 <= x <= 982 and 605 <= y <= 630: 124 | self.device_name_clicked() 125 | 126 | def update_data(self): 127 | if self.parent.trader.username: 128 | self.entry_3_email.delete(0, END) 129 | self.entry_3_email.insert(0, self.parent.trader.username) 130 | self.entry_2_password.focus() 131 | else: 132 | self.entry_3_email.focus() 133 | 134 | def msg_clicked(self, event): 135 | self.parent.show_info_message("Message example") 136 | 137 | def notify_clicked(self, event): 138 | self.parent.show_info_message("Message example") 139 | 140 | def device_name_clicked(self): 141 | input_value = simpledialog.askstring("Setup device name", 142 | "Enter the device name (click instruction for help) :", 143 | initialvalue=self.parent.db.get_device_name()) 144 | if input_value is not None: 145 | self.parent.set_device_name(input_value) 146 | 147 | def access_token_clicked(self): 148 | input_value = simpledialog.askstring( 149 | "Setup access token", 150 | "Enter the access token from the web, keep it safe (click instruction for help) :", 151 | initialvalue=self.parent.db.get_access_token()) 152 | if input_value is not None: 153 | self.parent.set_access_token(input_value) 154 | 155 | def setup_did_clicked(self): 156 | input_value = simpledialog.askstring("Setup _did value", "Enter the _did value (click instruction for help) : ", 157 | initialvalue=self.parent.db.get_did()) 158 | if input_value is not None: 159 | self.parent.setup_did(input_value) 160 | 161 | def setup_uuid_clicked(self): 162 | input_value = simpledialog.askstring("Setup uuid value", "Enter the uuid value (click instruction for help) : ", 163 | initialvalue=self.parent.db.get_uuid()) 164 | if input_value is not None: 165 | self.parent.setup_uuid(input_value) 166 | 167 | def instruction_clicked(self): 168 | # print(f"{self.name}: Instruction clicked") 169 | webbrowser.open("https://github.com/LukeWang01/Program-Trading-Based-on-Webull/blob/main/docs/first_run_setup.md") 170 | 171 | def login_clicked(self): 172 | logging_info("Login triggered") 173 | email = self.entry_3_email.get() 174 | password = self.entry_2_password.get() 175 | pid = self.entry_1_PID.get() 176 | if (is_valid_email(email) or is_valid_phone_number(email)) and is_valid_pid(pid): 177 | self.parent.login(email, password, pid) 178 | else: 179 | messagebox.showinfo("Oops something went wrong", "incorrect email, phone number, or PID format") 180 | -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/__init__.py -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/entry_1.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/entry_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/entry_2.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/entry_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/entry_3.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_1.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_2.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_3.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_4.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_5.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_6.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_7.png -------------------------------------------------------------------------------- /gui/gui_0_DashboardLogin/frame0/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_0_DashboardLogin/frame0/image_8.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/DashboardLogged.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, Text, PhotoImage 4 | 5 | from utils.format_str import format_financial_number 6 | 7 | 8 | class DashboardLogged(tk.Frame): 9 | def __init__(self, parent): 10 | tk.Frame.__init__(self, parent) 11 | self.parent = parent 12 | self.name = "DashboardLogged" 13 | 14 | OUTPUT_PATH = Path(__file__).parent 15 | ASSETS_PATH = OUTPUT_PATH / Path("frame1") 16 | 17 | def relative_to_assets(path: str) -> Path: 18 | return ASSETS_PATH / Path(path) 19 | 20 | self.canvas = Canvas( 21 | self, 22 | bg="#F1F5F9", 23 | height=728, 24 | width=1096, 25 | bd=0, 26 | highlightthickness=0, 27 | relief="ridge" 28 | ) 29 | self.canvas.place(x=0, y=0) 30 | 31 | self.image_image_1 = PhotoImage(file=relative_to_assets("image_1.png")) 32 | self.image_1 = self.canvas.create_image(548.0, 364.0, image=self.image_image_1) 33 | 34 | self.image_image_2 = PhotoImage(file=relative_to_assets("image_2.png")) 35 | self.image_2 = self.canvas.create_image(648.0, 385.0, image=self.image_image_2) 36 | 37 | self.image_image_3 = PhotoImage(file=relative_to_assets("image_3.png")) 38 | self.image_3_msg = self.canvas.create_image(1012.0, 30.0, image=self.image_image_3) 39 | 40 | self.image_image_4 = PhotoImage(file=relative_to_assets("image_4.png")) 41 | self.image_4_notify = self.canvas.create_image(971.0, 30.0, image=self.image_image_4) 42 | 43 | self.image_image_5 = PhotoImage(file=relative_to_assets("image_5.png")) 44 | self.image_5 = self.canvas.create_image(328.0, 30.0, image=self.image_image_5) 45 | 46 | self.image_image_6 = PhotoImage(file=relative_to_assets("image_6.png")) 47 | self.image_6 = self.canvas.create_image(534.0, 30.0, image=self.image_image_6) 48 | 49 | self.image_image_7 = PhotoImage(file=relative_to_assets("image_7.png")) 50 | self.image_7 = self.canvas.create_image(534.0, 30.0, image=self.image_image_7) 51 | 52 | self.image_image_8 = PhotoImage(file=relative_to_assets("image_8.png")) 53 | self.image_8 = self.canvas.create_image(328.0, 30.0, image=self.image_image_8) 54 | 55 | self.dji = self.canvas.create_text( 56 | 536.0, 57 | 705.0, 58 | anchor="nw", 59 | text="placeholder for dji", 60 | fill="#64748B", 61 | font=("ArialMT", 12 * -1) 62 | ) 63 | 64 | self.spx = self.canvas.create_text( 65 | 273.0, 66 | 705.0, 67 | anchor="nw", 68 | text="placeholder for spx", 69 | fill="#64748B", 70 | font=("ArialMT", 12 * -1) 71 | ) 72 | 73 | self.ixic = self.canvas.create_text( 74 | 404.0, 75 | 705.0, 76 | anchor="nw", 77 | text="placeholder for ndx", 78 | fill="#64748B", 79 | font=("ArialMT", 12 * -1) 80 | ) 81 | 82 | self.text_order_pending = self.canvas.create_text( 83 | 395.0, 84 | 320.0, 85 | anchor="nw", 86 | text="88888", 87 | fill="#64748B", 88 | font=("ArialMT", 16 * -1) 89 | ) 90 | 91 | self.text_net_account_value = self.canvas.create_text( 92 | 420.0, 93 | 521.0, 94 | anchor="nw", 95 | text="88888", 96 | fill="#64748B", 97 | font=("ArialMT", 16 * -1) 98 | ) 99 | 100 | self.text_cash_balance = self.canvas.create_text( 101 | 420.0, 102 | 559.0, 103 | anchor="nw", 104 | text="88888", 105 | fill="#64748B", 106 | font=("ArialMT", 16 * -1) 107 | ) 108 | 109 | self.text_market_value = self.canvas.create_text( 110 | 420.0, 111 | 599.0, 112 | anchor="nw", 113 | text="88888", 114 | fill="#64748B", 115 | font=("ArialMT", 16 * -1) 116 | ) 117 | 118 | self.text_dayPL = self.canvas.create_text( 119 | 370.0, 120 | 399.0, 121 | anchor="nw", 122 | text="888.88", 123 | fill="#64748B", 124 | font=("ArialMT", 16 * -1) 125 | ) 126 | 127 | self.text_openPL = self.canvas.create_text( 128 | 370.0, 129 | 444.0, 130 | anchor="nw", 131 | text="888.88", 132 | fill="#64748B", 133 | font=("ArialMT", 16 * -1) 134 | ) 135 | 136 | self.text_dayPL_pct = self.canvas.create_text( 137 | 465.0, 138 | 399.0, 139 | anchor="nw", 140 | text="8.88%", 141 | fill="#64748B", 142 | font=("ArialMT", 16 * -1) 143 | ) 144 | 145 | self.text_openPL_pct = self.canvas.create_text( 146 | 465.0, 147 | 444.0, 148 | anchor="nw", 149 | text="8.88%", 150 | fill="#64748B", 151 | font=("ArialMT", 16 * -1) 152 | ) 153 | 154 | self.text_order_filled = self.canvas.create_text( 155 | 395.0, 156 | 281.0, 157 | anchor="nw", 158 | text="88888", 159 | fill="#64748B", 160 | font=("ArialMT", 16 * -1) 161 | ) 162 | 163 | self.text_order_placed = self.canvas.create_text( 164 | 392.0, 165 | 242.0, 166 | anchor="nw", 167 | text="88888", 168 | fill="#64748B", 169 | font=("ArialMT", 16 * -1) 170 | ) 171 | 172 | self.text_account_id = self.canvas.create_text( 173 | 392.0, 174 | 184.0, 175 | anchor="nw", 176 | text="88888", 177 | fill="#64748B", 178 | font=("ArialMT", 16 * -1) 179 | ) 180 | 181 | self.text_email = self.canvas.create_text( 182 | 317.0, 183 | 135.0, 184 | anchor="nw", 185 | text="emailplaceholder@gmail.com", 186 | fill="#64748B", 187 | font=("ArialMT", 16 * -1) 188 | ) 189 | 190 | self.entry_image_1 = PhotoImage(file=relative_to_assets("entry_1.png")) 191 | self.entry_bg_1 = self.canvas.create_image(790.0, 400.0, image=self.entry_image_1) 192 | self.entry_1_my_positions = Text( 193 | self.canvas, 194 | bd=0, 195 | bg="#EFF4FB", 196 | fg="#000716", 197 | highlightthickness=0 198 | ) 199 | self.entry_1_my_positions.place(x=545.0, y=190.0, width=490.0, height=420.0) 200 | 201 | self.canvas.pack(fill="both", expand=True) 202 | 203 | self.canvas.bind("", self.frame_clicked) 204 | 205 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 206 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 207 | 208 | def frame_clicked(self, event): 209 | x = event.x 210 | y = event.y 211 | if x <= 200: 212 | # Sidebar area clicked 213 | self.parent.sidebar_clicked(x, y) 214 | elif 200 <= x <= 1096 and y <= 60: 215 | # Top bar area clicked 216 | self.parent.top_bar_clicked(x, y) 217 | else: 218 | # frame area clicked 219 | pass 220 | 221 | def update_data(self): 222 | self.update_market_status() 223 | self.parent.trader.update_account_info() 224 | self.update_text_email(self.parent.trader.username) 225 | self.update_text_account_id(self.parent.trader.account_id) 226 | self.update_text_order_placed(self.parent.trader.order_placed) 227 | self.update_text_order_filled(self.parent.trader.order_filled) 228 | self.update_text_order_pending(self.parent.trader.order_pending) 229 | self.update_text_openPL_pct(self.parent.trader.openPL_pct) 230 | self.update_text_openPL(format_financial_number(self.parent.trader.openPL)) 231 | self.update_text_dayPL_pct(self.parent.trader.dayPL_pct) 232 | self.update_text_dayPL(self.parent.trader.dayPL) 233 | self.update_text_market_value(format_financial_number(self.parent.trader.market_value)) 234 | self.update_text_cash_balance(format_financial_number(self.parent.trader.cash_balance)) 235 | self.update_text_net_account_value(format_financial_number(self.parent.trader.net_account_value)) 236 | self.update_entry_my_positions(self.parent.trader.my_positions) 237 | 238 | def msg_clicked(self, event): 239 | # print(f"{self.name}: Message clicked") 240 | # Not core functionality, implement later 241 | pass 242 | 243 | def notify_clicked(self, event): 244 | # print(f"{self.name}: Notify clicked") 245 | # Not core functionality, implement later 246 | pass 247 | 248 | def update_text_email(self, text): 249 | self.canvas.itemconfig(self.text_email, text=text) 250 | 251 | def update_text_account_id(self, text): 252 | self.canvas.itemconfig(self.text_account_id, text=text) 253 | 254 | def update_text_order_placed(self, text): 255 | self.canvas.itemconfig(self.text_order_placed, text=text) 256 | 257 | def update_text_order_filled(self, text): 258 | self.canvas.itemconfig(self.text_order_filled, text=text) 259 | 260 | def update_text_openPL_pct(self, text): 261 | self.canvas.itemconfig(self.text_openPL_pct, text=text) 262 | 263 | def update_text_dayPL_pct(self, text): 264 | self.canvas.itemconfig(self.text_dayPL_pct, text=text) 265 | 266 | def update_text_openPL(self, text): 267 | self.canvas.itemconfig(self.text_openPL, text=text) 268 | 269 | def update_text_dayPL(self, text): 270 | self.canvas.itemconfig(self.text_dayPL, text=text) 271 | 272 | def update_text_market_value(self, text): 273 | self.canvas.itemconfig(self.text_market_value, text=text) 274 | 275 | def update_text_cash_balance(self, text): 276 | self.canvas.itemconfig(self.text_cash_balance, text=text) 277 | 278 | def update_text_net_account_value(self, text): 279 | self.canvas.itemconfig(self.text_net_account_value, text=text) 280 | 281 | def update_entry_my_positions(self, text): 282 | self.entry_1_my_positions.delete("1.0", "end") 283 | self.entry_1_my_positions.insert("end", text) 284 | 285 | def update_text_order_pending(self, text): 286 | self.canvas.itemconfig(self.text_order_pending, text=text) 287 | 288 | def update_market_status(self): 289 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 290 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 291 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 292 | 293 | -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/__init__.py -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/entry_1.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_1.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_2.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_3.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_4.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_5.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_6.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_7.png -------------------------------------------------------------------------------- /gui/gui_1_DashboardLogged/frame1/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_1_DashboardLogged/frame1/image_8.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/StrategyMonitor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tkinter as tk 3 | from pathlib import Path 4 | from tkinter import Canvas, Entry, Text, PhotoImage, filedialog, messagebox 5 | 6 | import schedule 7 | 8 | from strategy.Strategy import Strategy 9 | from utils.dataIO import logging_info 10 | 11 | 12 | class StrategyMonitor(tk.Frame): 13 | def __init__(self, parent): 14 | # Initialize the frame 15 | tk.Frame.__init__(self, parent) 16 | 17 | self.name = "StrategyMonitor" 18 | self.parent = parent 19 | self.current = False 20 | 21 | self.imported_list = [] 22 | self.running_list = [] 23 | self.strategy_obj_list = [] 24 | # self.strategy_job_list = [] 25 | 26 | # UI elements: 27 | OUTPUT_PATH = Path(__file__).parent 28 | ASSETS_PATH = OUTPUT_PATH / Path(r"./frame2") 29 | 30 | def relative_to_assets(path: str) -> Path: 31 | return ASSETS_PATH / Path(path) 32 | 33 | self.canvas = Canvas( 34 | self, 35 | bg="#F1F5F9", 36 | height=728, 37 | width=1096, 38 | bd=0, 39 | highlightthickness=0, 40 | relief="ridge" 41 | ) 42 | self.canvas.place(x=0, y=0) 43 | 44 | # Load images 45 | self.image_image_1 = PhotoImage(file=relative_to_assets("image_1.png")) 46 | self.image_image_2 = PhotoImage(file=relative_to_assets("image_2.png")) 47 | self.image_image_3 = PhotoImage(file=relative_to_assets("image_3.png")) 48 | self.image_image_4 = PhotoImage(file=relative_to_assets("image_4.png")) 49 | self.image_image_5 = PhotoImage(file=relative_to_assets("image_5.png")) 50 | self.image_image_6 = PhotoImage(file=relative_to_assets("image_6.png")) 51 | self.image_image_7 = PhotoImage(file=relative_to_assets("image_7.png")) 52 | self.image_image_8 = PhotoImage(file=relative_to_assets("image_8.png")) 53 | self.entry_image_1 = PhotoImage(file=relative_to_assets("entry_1.png")) 54 | self.entry_image_2 = PhotoImage(file=relative_to_assets("entry_2.png")) 55 | self.entry_image_3 = PhotoImage(file=relative_to_assets("entry_3.png")) 56 | self.entry_image_4 = PhotoImage(file=relative_to_assets("entry_4.png")) 57 | self.entry_image_5 = PhotoImage(file=relative_to_assets("entry_5.png")) 58 | 59 | # Create canvas objects 60 | self.image_1 = self.canvas.create_image(548.0, 364.0, image=self.image_image_1) 61 | self.image_2 = self.canvas.create_image(648.0, 385.0, image=self.image_image_2) 62 | self.image_3_msg = self.canvas.create_image(1012.0, 30.0, image=self.image_image_3) 63 | self.image_4_notify = self.canvas.create_image(971.0, 30.0, image=self.image_image_4) 64 | self.image_5 = self.canvas.create_image(328.0, 30.0, image=self.image_image_5) 65 | self.image_6 = self.canvas.create_image(534.0, 30.0, image=self.image_image_6) 66 | self.image_7 = self.canvas.create_image(534.0, 30.0, image=self.image_image_7) 67 | self.image_8 = self.canvas.create_image(328.0, 30.0, image=self.image_image_8) 68 | 69 | self.dji = self.canvas.create_text( 70 | 536.0, 71 | 707.0, 72 | anchor="nw", 73 | text="placeholder for dji", 74 | fill="#64748B", 75 | font=("ArialMT", 12 * -1) 76 | ) 77 | 78 | self.spx = self.canvas.create_text( 79 | 273.0, 80 | 707.0, 81 | anchor="nw", 82 | text="placeholder for spx", 83 | fill="#64748B", 84 | font=("ArialMT", 12 * -1) 85 | ) 86 | 87 | self.ixic = self.canvas.create_text( 88 | 404.0, 89 | 707.0, 90 | anchor="nw", 91 | text="placeholder for ndx", 92 | fill="#64748B", 93 | font=("ArialMT", 12 * -1) 94 | ) 95 | 96 | self.strategy_status = self.canvas.create_text( 97 | 405.0, 98 | 176.0, 99 | anchor="nw", 100 | text="running", 101 | fill="#64748B", 102 | font=("ArialMT", 16 * -1) 103 | ) 104 | 105 | self.current_strategy = self.canvas.create_text( 106 | 405.0, 107 | 135.0, 108 | anchor="nw", 109 | text=" - ", 110 | fill="#64748B", 111 | font=("ArialMT", 16 * -1) 112 | ) 113 | 114 | self.entry_bg_1 = self.canvas.create_image(840.0, 547.0, image=self.entry_image_1) 115 | self.entry_1_strategy_stream = Text(self.canvas, bd=0, bg="#EFF4FB", fg="#000716", highlightthickness=0) 116 | self.entry_1_strategy_stream.place(x=669.0, y=472.0, width=342.0, height=148.0) 117 | 118 | self.entry_bg_2 = self.canvas.create_image(840.0, 314.0, image=self.entry_image_2) 119 | self.entry_2_quoter_stream = Text(self.canvas, bd=0, bg="#EFF4FB", fg="#000716", highlightthickness=0) 120 | self.entry_2_quoter_stream.place(x=669.0, y=204.0, width=342.0, height=218.0) 121 | 122 | self.entry_bg_3 = self.canvas.create_image(445.0, 364.0, image=self.entry_image_3) 123 | self.entry_3_imported_list = Text(self.canvas, bd=0, bg="#EFF4FB", fg="#000716", highlightthickness=0) 124 | self.entry_3_imported_list.place(x=274.0, y=304.0, width=342.0, height=118.0) 125 | 126 | self.entry_bg_4 = self.canvas.create_image(485.0, 473.5, image=self.entry_image_4) 127 | self.entry_4_strategy_quoter = Entry(self.canvas, bd=0, bg="#FFFFFF", fg="#000716", highlightthickness=0) 128 | self.entry_4_strategy_quoter.place(x=409.0, y=461.0, width=152.0, height=23.0) 129 | 130 | self.entry_bg_5 = self.canvas.create_image(485.0, 509.5, image=self.entry_image_5) 131 | self.entry_5_monitor_ticker = Entry(self.canvas, bd=0, bg="#FFFFFF", fg="#000716", highlightthickness=0) 132 | self.entry_5_monitor_ticker.place(x=409.0, y=497.0, width=152.0, height=23.0) 133 | 134 | self.canvas.pack(fill="both", expand=True) 135 | 136 | self.canvas.bind("", self.frame_clicked) 137 | 138 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 139 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 140 | 141 | def frame_clicked(self, event): 142 | x = event.x 143 | y = event.y 144 | if x <= 200: 145 | # Sidebar area clicked 146 | self.parent.sidebar_clicked(x, y) 147 | elif 200 <= x <= 1096 and y <= 60: 148 | # Top bar area clicked 149 | self.parent.top_bar_clicked(x, y) 150 | else: 151 | # frame area clicked 152 | if 425 <= x <= 551 and 225 <= y <= 251: 153 | self.select_strategy() 154 | elif 285 <= x <= 409 and 588 <= y <= 617: 155 | self.run_strategy() 156 | elif 444 <= x <= 568 and 596 <= y <= 626: 157 | self.cancel_strategy() 158 | 159 | def update_data(self): 160 | self.update_market_status() 161 | 162 | def select_strategy(self): 163 | relative_path = "strategy" # Relative path of the folder under the current folder 164 | initial_dir = os.path.join(os.getcwd(), relative_path) 165 | file_path = filedialog.askopenfilename(title="Select a file", initialdir=initial_dir, 166 | filetypes=[("Python files", "*.py"), ("All files", "*.*")]) 167 | 168 | if file_path: 169 | file_name = os.path.basename(file_path) 170 | # print("Selected file:", file_name) 171 | if file_name not in self.imported_list: 172 | self.set_imported_list(file_name) 173 | self.imported_list.append(file_name) 174 | self.set_current_strategy(file_name) 175 | logging_info(f"Imported strategy: {file_name}") 176 | 177 | def run_strategy(self): 178 | if self.parent.trader.is_trader_logged_in(): 179 | if self.imported_list: 180 | if set(self.imported_list) == set(self.running_list): 181 | messagebox.showinfo("Oops", "All strategies are running!") 182 | else: 183 | for file_name in self.imported_list: 184 | if file_name in self.running_list: 185 | continue 186 | strategy_obj = self.get_strategy_obj(file_name) 187 | schedule.every().minute.at(":01").do(strategy_obj.strategy_decision).tag('strategy_jobs') 188 | 189 | self.running_list.append(file_name) 190 | self.strategy_obj_list.append(strategy_obj) 191 | print('running strategy: ', file_name) 192 | logging_info(f"Add running strategy: {file_name}") 193 | self.parent.show_info_message(f"Running strategy: {file_name}") 194 | # print(self.imported_list) 195 | # print(self.running_list) 196 | # print(self.strategy_obj_list) 197 | else: 198 | messagebox.showinfo("Oops", "Please select a strategy first!") 199 | else: 200 | self.parent.logged_in = False 201 | messagebox.showinfo("Oops", "To run the strategy, please login first!") 202 | 203 | def get_strategy_obj(self, filename) -> Strategy: 204 | strategy_name = filename[:-3] 205 | namespace = {} 206 | new_strategy_code = f""" 207 | from strategy.{strategy_name} import {strategy_name} 208 | strategy_instance = {strategy_name}() 209 | """ 210 | # dynamic code should start without indentation 211 | exec(new_strategy_code, namespace) 212 | strategy_obj = namespace['strategy_instance'] 213 | strategy_obj.parent = self.parent 214 | strategy_obj.update_strategy_profile() 215 | return strategy_obj 216 | 217 | def cancel_strategy(self): 218 | schedule.clear('strategy_jobs') 219 | self.imported_list = [] 220 | self.running_list = [] 221 | self.strategy_obj_list = [] 222 | self.empty_imported_list() 223 | self.parent.show_info_message("All strategies are cancelled!") 224 | logging_info("Strategies cancelled!") 225 | 226 | def msg_clicked(self, event): 227 | # print(f"{self.name}: Message clicked") 228 | # Not core function, implement later 229 | pass 230 | 231 | def notify_clicked(self, event): 232 | # print(f"{self.name}: Notify clicked") 233 | # Not core function, implement later 234 | pass 235 | 236 | def update_market_status(self): 237 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 238 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 239 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 240 | 241 | def set_quoter_stream(self, text): 242 | # txt = self.get_quoter_stream() + text 243 | # self.entry_2_quoter_stream.delete(1.0, tk.END) 244 | self.entry_2_quoter_stream.insert(tk.END, text) 245 | self.entry_2_quoter_stream.see(tk.END) 246 | 247 | def get_quoter_stream(self): 248 | return self.entry_2_quoter_stream.get(1.0, tk.END) 249 | 250 | def set_strategy_stream(self, text): 251 | txt = self.get_strategy_stream() + text 252 | self.entry_1_strategy_stream.delete(1.0, tk.END) 253 | self.entry_1_strategy_stream.insert(1.0, txt) 254 | self.entry_1_strategy_stream.see(tk.END) 255 | 256 | def get_strategy_stream(self): 257 | return self.entry_1_strategy_stream.get(1.0, tk.END) 258 | 259 | def set_imported_list(self, text): 260 | txt = self.get_imported_list() + text 261 | self.entry_3_imported_list.delete(1.0, tk.END) 262 | self.entry_3_imported_list.insert(1.0, txt) 263 | self.entry_3_imported_list.see(tk.END) 264 | 265 | def empty_imported_list(self): 266 | self.entry_3_imported_list.delete(1.0, tk.END) 267 | 268 | def get_imported_list(self): 269 | return self.entry_3_imported_list.get(1.0, tk.END) 270 | 271 | def set_current_strategy(self, text): 272 | self.canvas.itemconfig(self.current_strategy, text=text) 273 | -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/__init__.py -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/entry_1.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/entry_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/entry_2.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/entry_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/entry_3.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/entry_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/entry_4.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/entry_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/entry_5.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_1.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_2.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_3.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_4.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_5.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_6.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_7.png -------------------------------------------------------------------------------- /gui/gui_2_StrategyMonitor/frame2/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_2_StrategyMonitor/frame2/image_8.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/TreaderProfile.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, Entry, Text, PhotoImage, messagebox 4 | 5 | from utils.dataIO import logging_info 6 | from utils.input_check import is_valid_email 7 | 8 | 9 | class TreaderProfile(tk.Frame): 10 | def __init__(self, parent): 11 | # Initialize the frame 12 | tk.Frame.__init__(self, parent) 13 | self.name = "TreaderProfile" 14 | self.parent = parent 15 | self.current = False 16 | 17 | self.save_user_email = 1 18 | 19 | # UI elements: 20 | OUTPUT_PATH = Path(__file__).parent 21 | ASSETS_PATH = OUTPUT_PATH / Path("./frame3") 22 | 23 | def relative_to_assets(path: str) -> Path: 24 | return ASSETS_PATH / Path(path) 25 | 26 | self.canvas = Canvas( 27 | self, 28 | bg="#F1F5F9", 29 | height=728, 30 | width=1096, 31 | bd=0, 32 | highlightthickness=0, 33 | relief="ridge" 34 | ) 35 | self.canvas.place(x=0, y=0) 36 | 37 | self.image_image_1 = PhotoImage( 38 | file=relative_to_assets("image_1.png")) 39 | self.image_1 = self.canvas.create_image( 40 | 548.0, 41 | 364.0, 42 | image=self.image_image_1 43 | ) 44 | 45 | self.image_image_2 = PhotoImage( 46 | file=relative_to_assets("image_2.png")) 47 | self.image_2 = self.canvas.create_image( 48 | 648.0, 49 | 385.0, 50 | image=self.image_image_2 51 | ) 52 | 53 | self.image_image_3 = PhotoImage( 54 | file=relative_to_assets("image_3.png")) 55 | self.image_3_msg = self.canvas.create_image( 56 | 1012.0, 57 | 30.0, 58 | image=self.image_image_3 59 | ) 60 | 61 | self.image_image_4 = PhotoImage( 62 | file=relative_to_assets("image_4.png")) 63 | self.image_4_notify = self.canvas.create_image( 64 | 971.0, 65 | 30.0, 66 | image=self.image_image_4 67 | ) 68 | 69 | self.image_image_5 = PhotoImage( 70 | file=relative_to_assets("image_5.png")) 71 | self.image_5 = self.canvas.create_image( 72 | 328.0, 73 | 30.0, 74 | image=self.image_image_5 75 | ) 76 | 77 | self.image_image_6 = PhotoImage( 78 | file=relative_to_assets("image_6.png")) 79 | self.image_6 = self.canvas.create_image( 80 | 534.0, 81 | 30.0, 82 | image=self.image_image_6 83 | ) 84 | 85 | self.image_image_7 = PhotoImage( 86 | file=relative_to_assets("image_7.png")) 87 | self.image_7 = self.canvas.create_image( 88 | 534.0, 89 | 30.0, 90 | image=self.image_image_7 91 | ) 92 | 93 | self.image_image_8 = PhotoImage( 94 | file=relative_to_assets("image_8.png")) 95 | self.image_8 = self.canvas.create_image( 96 | 328.0, 97 | 30.0, 98 | image=self.image_image_8 99 | ) 100 | 101 | self.dji = self.canvas.create_text( 102 | 536.0, 103 | 707.0, 104 | anchor="nw", 105 | text="placeholder for dji", 106 | fill="#64748B", 107 | font=("ArialMT", 12 * -1) 108 | ) 109 | 110 | self.spx = self.canvas.create_text( 111 | 273.0, 112 | 707.0, 113 | anchor="nw", 114 | text="placeholder for spx", 115 | fill="#64748B", 116 | font=("ArialMT", 12 * -1) 117 | ) 118 | 119 | self.ixic = self.canvas.create_text( 120 | 404.0, 121 | 707.0, 122 | anchor="nw", 123 | text="placeholder for ndx", 124 | fill="#64748B", 125 | font=("ArialMT", 12 * -1) 126 | ) 127 | 128 | self.account_type = self.canvas.create_text( 129 | 411.0, 130 | 165.0, 131 | anchor="nw", 132 | text="1 or 2, email or phone", 133 | fill="#64748B", 134 | font=("ArialMT", 12) 135 | ) 136 | 137 | self.account_id = self.canvas.create_text( 138 | 411.0, 139 | 135.0, 140 | anchor="nw", 141 | text="88888888", 142 | fill="#64748B", 143 | font=("ArialMT", 12) 144 | ) 145 | 146 | self.entry_image_1 = PhotoImage( 147 | file=relative_to_assets("entry_1.png")) 148 | self.entry_bg_1 = self.canvas.create_image( 149 | 501.0, 150 | 416.5, 151 | image=self.entry_image_1 152 | ) 153 | self.entry_1_PID_expired = Entry( 154 | self.canvas, 155 | bd=0, 156 | bg="#FFFFFF", 157 | fg="#000716", 158 | highlightthickness=0 159 | ) 160 | self.entry_1_PID_expired.place( 161 | x=425.0, 162 | y=404.0, 163 | width=152.0, 164 | height=23.0 165 | ) 166 | 167 | self.entry_image_2 = PhotoImage( 168 | file=relative_to_assets("entry_2.png")) 169 | self.entry_bg_2 = self.canvas.create_image( 170 | 501.0, 171 | 251.5, 172 | image=self.entry_image_2 173 | ) 174 | self.entry_2_sender_email = Entry( 175 | self.canvas, 176 | bd=0, 177 | bg="#FFFFFF", 178 | fg="#000716", 179 | highlightthickness=0 180 | ) 181 | self.entry_2_sender_email.place( 182 | x=425.0, 183 | y=239.0, 184 | width=152.0, 185 | height=23.0 186 | ) 187 | 188 | self.entry_image_3 = PhotoImage( 189 | file=relative_to_assets("entry_3.png")) 190 | self.entry_bg_3 = self.canvas.create_image( 191 | 501.0, 192 | 293.5, 193 | image=self.entry_image_3 194 | ) 195 | self.entry_3_sender_password = Entry( 196 | self.canvas, 197 | bd=0, 198 | bg="#FFFFFF", 199 | fg="#000716", 200 | highlightthickness=0, 201 | show="*" 202 | ) 203 | self.entry_3_sender_password.place( 204 | x=425.0, 205 | y=281.0, 206 | width=152.0, 207 | height=23.0 208 | ) 209 | 210 | self.entry_image_4 = PhotoImage( 211 | file=relative_to_assets("entry_4.png")) 212 | self.entry_bg_4 = self.canvas.create_image( 213 | 501.0, 214 | 338.5, 215 | image=self.entry_image_4 216 | ) 217 | self.entry_4_receiver_email = Entry( 218 | self.canvas, 219 | bd=0, 220 | bg="#FFFFFF", 221 | fg="#000716", 222 | highlightthickness=0 223 | ) 224 | self.entry_4_receiver_email.place( 225 | x=425.0, 226 | y=326.0, 227 | width=152.0, 228 | height=23.0 229 | ) 230 | 231 | self.entry_image_5 = PhotoImage( 232 | file=relative_to_assets("entry_5.png")) 233 | self.entry_bg_5 = self.canvas.create_image( 234 | 840.0, 235 | 389.0, 236 | image=self.entry_image_5 237 | ) 238 | self.entry_5_future_use = Text( 239 | self.canvas, 240 | bd=0, 241 | bg="#EFF4FB", 242 | fg="#000716", 243 | highlightthickness=0 244 | ) 245 | self.entry_5_future_use.place( 246 | x=669.0, 247 | y=204.0, 248 | width=342.0, 249 | height=368.0 250 | ) 251 | 252 | self.image_image_9 = PhotoImage( 253 | file=relative_to_assets("image_9.png")) 254 | self.image_9_check_box_0 = self.canvas.create_image( 255 | 431.0, 256 | 458.0, 257 | image=self.image_image_9 258 | ) 259 | 260 | self.image_image_10 = PhotoImage( 261 | file=relative_to_assets("image_10.png")) 262 | self.image_10_check_box_1 = self.canvas.create_image( 263 | 431.0, 264 | 458.0, 265 | image=self.image_image_10 266 | ) 267 | 268 | self.canvas.pack(fill="both", expand=True) 269 | 270 | self.canvas.bind("", self.frame_clicked) 271 | 272 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 273 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 274 | 275 | def frame_clicked(self, event): 276 | x = event.x 277 | y = event.y 278 | if x <= 200: 279 | # Sidebar area clicked 280 | self.parent.sidebar_clicked(x, y) 281 | elif 200 <= x <= 1096 and y <= 60: 282 | # Top bar area clicked 283 | self.parent.top_bar_clicked(x, y) 284 | else: 285 | # frame area clicked 286 | if 378 <= x <= 502 and 596 <= y <= 626: 287 | self.update_profile_clicked() 288 | elif 421 <= x <= 441 and 448 <= y <= 468: 289 | self.save_user_email_clicked() 290 | 291 | def update_data(self): 292 | self.update_market_status() 293 | 294 | self.set_account_id(self.parent.trader.account_id) 295 | self.set_account_type(self.parent.trader.account_type) 296 | 297 | self.set_sender_email(self.parent.sender_email) 298 | self.set_sender_password(self.parent.sender_password) 299 | self.set_receiver_email_1(self.parent.receiver_email_1) 300 | self.save_user_email = self.parent.save_user_email 301 | if self.save_user_email: 302 | self.canvas.itemconfig(self.image_9_check_box_0, state="hidden") 303 | self.canvas.itemconfig(self.image_10_check_box_1, state="normal") 304 | else: 305 | self.canvas.itemconfig(self.image_9_check_box_0, state="normal") 306 | self.canvas.itemconfig(self.image_10_check_box_1, state="hidden") 307 | self.set_PID_expired() 308 | 309 | def save_user_email_clicked(self): 310 | if self.save_user_email: 311 | self.save_user_email = 0 312 | self.canvas.itemconfig(self.image_9_check_box_0, state="normal") 313 | self.canvas.itemconfig(self.image_10_check_box_1, state="hidden") 314 | # print(self.save_user_email) 315 | else: 316 | self.save_user_email = 1 317 | self.canvas.itemconfig(self.image_9_check_box_0, state="hidden") 318 | self.canvas.itemconfig(self.image_10_check_box_1, state="normal") 319 | # print(self.save_user_email) 320 | 321 | def update_profile_clicked(self): 322 | # print(f"{self.name}: Update profile clicked") 323 | sender_email = self.entry_2_sender_email.get() 324 | sender_password = self.entry_3_sender_password.get() 325 | receiver_email_1 = self.entry_4_receiver_email.get() 326 | 327 | save_user_email = self.save_user_email 328 | pid_expire = self.entry_1_PID_expired.get() 329 | try: 330 | pid_expire = int(pid_expire) 331 | if is_valid_email(sender_email) and is_valid_email(receiver_email_1): 332 | self.parent.set_trader_profile_state(sender_email, sender_password, receiver_email_1, save_user_email, 333 | pid_expire) 334 | logging_info("Update profile success") 335 | else: 336 | messagebox.showerror("Oops something went wrong", "Invalid email address") 337 | except: 338 | messagebox.showerror("Oops something went wrong", "Invalid PID expire input, must be integer") 339 | 340 | def msg_clicked(self, event): 341 | # Not core feature, implement later 342 | pass 343 | 344 | def notify_clicked(self, event): 345 | # print(f"{self.name}: Notify clicked") 346 | # Not core feature, implement later 347 | pass 348 | 349 | def update_market_status(self): 350 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 351 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 352 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 353 | 354 | def set_sender_email(self, sender_email): 355 | self.entry_2_sender_email.delete(0, tk.END) 356 | self.entry_2_sender_email.insert(0, sender_email) 357 | 358 | def set_sender_password(self, sender_password): 359 | self.entry_3_sender_password.delete(0, tk.END) 360 | self.entry_3_sender_password.insert(0, sender_password) 361 | 362 | def set_receiver_email_1(self, receiver_email_1): 363 | self.entry_4_receiver_email.delete(0, tk.END) 364 | self.entry_4_receiver_email.insert(0, receiver_email_1) 365 | 366 | def set_PID_expired(self): 367 | time_out = self.parent.PID_timeout 368 | self.entry_1_PID_expired.delete(0, tk.END) 369 | self.entry_1_PID_expired.insert(0, str(time_out)) 370 | 371 | def set_account_id(self, account_id): 372 | self.canvas.itemconfig(self.account_id, text=account_id) 373 | 374 | def set_account_type(self, account_type): 375 | self.canvas.itemconfig(self.account_type, text=account_type) 376 | -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/__init__.py -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/entry_1.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/entry_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/entry_2.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/entry_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/entry_3.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/entry_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/entry_4.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/entry_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/entry_5.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_1.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_10.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_2.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_3.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_4.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_5.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_6.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_7.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_8.png -------------------------------------------------------------------------------- /gui/gui_3_TreaderProfile/frame3/image_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_3_TreaderProfile/frame3/image_9.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/TradingList.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, Text, PhotoImage 4 | 5 | from utils.dataIO import logging_info, logging_error 6 | 7 | 8 | class TradingList(tk.Frame): 9 | def __init__(self, parent): 10 | # Initialize the frame 11 | tk.Frame.__init__(self, parent) 12 | self.name = "TradingList" 13 | self.parent = parent 14 | self.current = False 15 | 16 | # UI elements: 17 | OUTPUT_PATH = Path(__file__).parent 18 | ASSETS_PATH = OUTPUT_PATH / Path(r"./frame4") 19 | 20 | def relative_to_assets(path: str) -> Path: 21 | return ASSETS_PATH / Path(path) 22 | 23 | self.canvas = Canvas( 24 | self, 25 | bg="#F1F5F9", 26 | height=728, 27 | width=1096, 28 | bd=0, 29 | highlightthickness=0, 30 | relief="ridge" 31 | ) 32 | self.canvas.place(x=0, y=0) 33 | 34 | # Store references to the PhotoImage objects 35 | self.image_1 = PhotoImage(file=relative_to_assets("image_1.png")) 36 | self.image_2 = PhotoImage(file=relative_to_assets("image_2.png")) 37 | self.image_3 = PhotoImage(file=relative_to_assets("image_3.png")) 38 | self.image_4 = PhotoImage(file=relative_to_assets("image_4.png")) 39 | self.image_5 = PhotoImage(file=relative_to_assets("image_5.png")) 40 | self.image_6 = PhotoImage(file=relative_to_assets("image_6.png")) 41 | self.image_7 = PhotoImage(file=relative_to_assets("image_7.png")) 42 | self.image_8 = PhotoImage(file=relative_to_assets("image_8.png")) 43 | self.entry_image_1 = PhotoImage(file=relative_to_assets("entry_1.png")) 44 | self.entry_image_2 = PhotoImage(file=relative_to_assets("entry_2.png")) 45 | 46 | self.image_image_1 = self.canvas.create_image(548.0, 364.0, image=self.image_1) 47 | self.image_image_2 = self.canvas.create_image(648.0, 385.0, image=self.image_2) 48 | self.image_3_msg = self.canvas.create_image(1012.0, 30.0, image=self.image_3) 49 | self.image_4_notify = self.canvas.create_image(971.0, 30.0, image=self.image_4) 50 | self.image_image_5 = self.canvas.create_image(328.0, 30.0, image=self.image_5) 51 | self.image_image_6 = self.canvas.create_image(534.0, 30.0, image=self.image_6) 52 | self.image_image_7 = self.canvas.create_image(534.0, 30.0, image=self.image_7) 53 | self.image_image_8 = self.canvas.create_image(328.0, 30.0, image=self.image_8) 54 | 55 | self.dji = self.canvas.create_text( 56 | 536.0, 57 | 707.0, 58 | anchor="nw", 59 | text="placeholder for dji", 60 | fill="#64748B", 61 | font=("ArialMT", 12 * -1) 62 | ) 63 | 64 | self.spx = self.canvas.create_text( 65 | 273.0, 66 | 707.0, 67 | anchor="nw", 68 | text="placeholder for spx", 69 | fill="#64748B", 70 | font=("ArialMT", 12 * -1) 71 | ) 72 | 73 | self.ixic = self.canvas.create_text( 74 | 404.0, 75 | 707.0, 76 | anchor="nw", 77 | text="placeholder for ndx", 78 | fill="#64748B", 79 | font=("ArialMT", 12 * -1) 80 | ) 81 | 82 | entry_bg_1 = self.canvas.create_image(443.0, 381.0, image=self.entry_image_1) 83 | self.entry_1_order_list = Text( 84 | self.canvas, 85 | bd=0, 86 | bg="#EFF4FB", 87 | fg="#000716", 88 | highlightthickness=0 89 | ) 90 | self.entry_1_order_list.place( 91 | x=272.0, 92 | y=181.0, 93 | width=342.0, 94 | height=398.0 95 | ) 96 | 97 | entry_bg_2 = self.canvas.create_image(846.0, 381.0, image=self.entry_image_2) 98 | self.entry_2_transactions = Text( 99 | self.canvas, 100 | bd=0, 101 | bg="#EFF4FB", 102 | fg="#000716", 103 | highlightthickness=0 104 | ) 105 | self.entry_2_transactions.place( 106 | x=675.0, 107 | y=181.0, 108 | width=342.0, 109 | height=398.0 110 | ) 111 | 112 | self.canvas.pack(fill="both", expand=True) 113 | 114 | self.canvas.bind("", self.frame_clicked) 115 | 116 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 117 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 118 | 119 | def frame_clicked(self, event): 120 | x = event.x 121 | y = event.y 122 | if x <= 200: 123 | # Sidebar area clicked 124 | # print("Sidebar clicked, frame0") 125 | self.parent.sidebar_clicked(x, y) 126 | elif 200 <= x <= 1096 and y <= 60: 127 | # Top bar area clicked 128 | # print("Top_bar clicked, frame0") 129 | self.parent.top_bar_clicked(x, y) 130 | else: 131 | # frame area clicked 132 | pass 133 | 134 | def update_data(self): 135 | self.update_market_status() 136 | self.set_order_list() 137 | self.set_transactions() 138 | 139 | def msg_clicked(self, event): 140 | # print(f"{self.name}: Message clicked") 141 | # not core function, implement later 142 | pass 143 | 144 | def notify_clicked(self, event): 145 | # print(f"{self.name}: Notify clicked") 146 | # not core function, implement later 147 | pass 148 | 149 | def get_order_list(self): 150 | try: 151 | data = self.parent.trader.get_pending_orders_history() 152 | except Exception as e: 153 | # print(e) 154 | logging_error(str(e)) 155 | return "Webull API changed, no order found, please contact developer." 156 | res_str = '' 157 | if data: 158 | for order in data: 159 | line_str = '' 160 | try: 161 | order_date = f"Create Date: {order['orders'][0]['createTime']}\n" 162 | order_list_header = 'Ticker, Action, Qty, Price, Amount\n' 163 | Ticker = order['orders'][0]['symbol'] 164 | Action = order['orders'][0]['action'] 165 | Qty = order['orders'][0]['totalQuantity'] 166 | Price = 0 167 | if order['orders'][0]['orderType'] == 'LMT': 168 | Price = order['orders'][0]['lmtPrice'] 169 | elif order['orders'][0]['orderType'] == 'STP': 170 | Price = order['orders'][0]['auxPrice'] 171 | Amount = order['orders'][0]['placeAmount'] 172 | tmp_line = order_list_header + f"{Ticker}, {Action}, {Qty}, {Price}, {Amount}\n" 173 | column_widths = [8, 8, 5, 8, 10] 174 | formatted = '' 175 | # Format and print the data with aligned columns 176 | for line in tmp_line.split("\n"): 177 | cells = line.split(",") 178 | formatted += "".join(cell.strip().ljust(column_width) for cell, column_width in zip(cells, column_widths)) + "\n" 179 | line_str += order_date + formatted + '-----------------------------' + '\n' 180 | except: 181 | line_str = 'Order type not supported \n' 182 | res_str += line_str 183 | logging_info('Get trader order list successfully') 184 | return res_str 185 | else: 186 | return 'No pending orders' 187 | 188 | def get_transactions(self): 189 | data = self.parent.trader.get_filled_orders_history() 190 | res_str = '' 191 | if data: 192 | for order in data: 193 | if order['status'] == 'Filled': 194 | line_str = '' 195 | order_date = f"Filled Date: {order['orders'][0]['filledTime']}\n" 196 | order_list_header = 'Ticker, Action, Qty, Price, Amount\n' 197 | Ticker = order['orders'][0]['symbol'] 198 | Action = order['orders'][0]['action'] 199 | Qty = order['orders'][0]['filledQuantity'] 200 | Price = order['orders'][0]['avgFilledPrice'] 201 | Amount = order['orders'][0]['filledAmount'] 202 | tmp_line = order_list_header + f"{Ticker}, {Action}, {Qty}, {Price}, {Amount}\n" 203 | column_widths = [8, 8, 5, 8, 10] 204 | formatted = '' 205 | # Format and print the data with aligned columns 206 | for line in tmp_line.split("\n"): 207 | cells = line.split(",") 208 | formatted += "".join( 209 | cell.strip().ljust(column_width) for cell, column_width in zip(cells, column_widths)) + "\n" 210 | line_str += order_date + formatted + '-----------------------------' + '\n' 211 | res_str += line_str 212 | logging_info('Get trader transaction list successfully') 213 | return res_str 214 | else: 215 | return 'No filled orders' 216 | 217 | def set_order_list(self): 218 | order_list = self.get_order_list() 219 | self.entry_1_order_list.delete(1.0, tk.END) 220 | self.entry_1_order_list.insert(1.0, order_list) 221 | # self.entry_1_order_list.see(tk.END) 222 | 223 | def set_transactions(self): 224 | transactions = self.get_transactions() 225 | self.entry_2_transactions.delete(1.0, tk.END) 226 | self.entry_2_transactions.insert(1.0, transactions) 227 | # self.entry_2_transactions.see(tk.END) 228 | 229 | def update_market_status(self): 230 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 231 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 232 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 233 | 234 | -------------------------------------------------------------------------------- /gui/gui_4_TradingList/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/__init__.py -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/entry_1.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/entry_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/entry_2.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_1.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_2.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_3.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_4.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_5.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_6.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_7.png -------------------------------------------------------------------------------- /gui/gui_4_TradingList/frame4/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_4_TradingList/frame4/image_8.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/Performance.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, PhotoImage 4 | 5 | 6 | class Performance(tk.Frame): 7 | def __init__(self, parent): 8 | # Initialize the Frame object 9 | tk.Frame.__init__(self, parent) 10 | self.name = "Performance" 11 | self.parent = parent 12 | self.current = False 13 | 14 | # UI elements: 15 | OUTPUT_PATH = Path(__file__).parent 16 | ASSETS_PATH = OUTPUT_PATH / Path(r"./frame5") 17 | 18 | def relative_to_assets(path: str) -> Path: 19 | return ASSETS_PATH / Path(path) 20 | 21 | self.canvas = Canvas( 22 | self, 23 | bg="#F1F5F9", 24 | height=728, 25 | width=1096, 26 | bd=0, 27 | highlightthickness=0, 28 | relief="ridge" 29 | ) 30 | self.canvas.place(x=0, y=0) 31 | 32 | # Store references to the PhotoImage objects 33 | self.image_1 = PhotoImage(file=relative_to_assets("image_1.png")) 34 | self.image_2 = PhotoImage(file=relative_to_assets("image_2.png")) 35 | self.image_3 = PhotoImage(file=relative_to_assets("image_3.png")) 36 | self.image_4 = PhotoImage(file=relative_to_assets("image_4.png")) 37 | self.image_5 = PhotoImage(file=relative_to_assets("image_5.png")) 38 | self.image_6 = PhotoImage(file=relative_to_assets("image_6.png")) 39 | self.image_7 = PhotoImage(file=relative_to_assets("image_7.png")) 40 | self.image_8 = PhotoImage(file=relative_to_assets("image_8.png")) 41 | 42 | self.image_image_1 = self.canvas.create_image(548.0, 364.0, image=self.image_1) 43 | self.image_image_2 = self.canvas.create_image(648.0, 385.0, image=self.image_2) 44 | self.image_3_msg = self.canvas.create_image(1012.0, 30.0, image=self.image_3) 45 | self.image_4_notify = self.canvas.create_image(971.0, 30.0, image=self.image_4) 46 | self.image_image_5 = self.canvas.create_image(328.0, 30.0, image=self.image_5) 47 | self.image_image_6 = self.canvas.create_image(534.0, 30.0, image=self.image_6) 48 | self.image_image_7 = self.canvas.create_image(534.0, 30.0, image=self.image_7) 49 | self.image_image_8 = self.canvas.create_image(328.0, 30.0, image=self.image_8) 50 | 51 | self.dji = self.canvas.create_text( 52 | 536.0, 53 | 707.0, 54 | anchor="nw", 55 | text="placeholder for dji", 56 | fill="#64748B", 57 | font=("ArialMT", 12 * -1) 58 | ) 59 | self.spx = self.canvas.create_text( 60 | 273.0, 61 | 707.0, 62 | anchor="nw", 63 | text="placeholder for spx", 64 | fill="#64748B", 65 | font=("ArialMT", 12 * -1) 66 | ) 67 | self.ixic = self.canvas.create_text( 68 | 404.0, 69 | 707.0, 70 | anchor="nw", 71 | text="placeholder for ndx", 72 | fill="#64748B", 73 | font=("ArialMT", 12 * -1) 74 | ) 75 | self.total_PL = self.canvas.create_text( 76 | 450.0, 77 | 135.0, 78 | anchor="nw", 79 | text="Total P/L", 80 | fill="#64748B", 81 | font=("ArialMT", 16 * -1) 82 | ) 83 | self.day_PL = self.canvas.create_text( 84 | 449.0, 85 | 177.0, 86 | anchor="nw", 87 | text="Day P/L", 88 | fill="#64748B", 89 | font=("ArialMT", 16 * -1) 90 | ) 91 | self.openPL = self.canvas.create_text( 92 | 450.0, 93 | 225.0, 94 | anchor="nw", 95 | text="Open for P/L", 96 | fill="#64748B", 97 | font=("ArialMT", 16 * -1) 98 | ) 99 | self.cash_balance = self.canvas.create_text( 100 | 449.0, 101 | 256.0, 102 | anchor="nw", 103 | text="Cash for P/L", 104 | fill="#64748B", 105 | font=("ArialMT", 16 * -1) 106 | ) 107 | self.market_value = self.canvas.create_text( 108 | 449.0, 109 | 294.0, 110 | anchor="nw", 111 | text="Market for P/L", 112 | fill="#64748B", 113 | font=("ArialMT", 16 * -1) 114 | ) 115 | self.weekPL = self.canvas.create_text( 116 | 449.0, 117 | 370.0, 118 | anchor="nw", 119 | text="week for P/L", 120 | fill="#64748B", 121 | font=("ArialMT", 16 * -1) 122 | ) 123 | self.monthPL = self.canvas.create_text( 124 | 449.0, 125 | 411.0, 126 | anchor="nw", 127 | text="month for P/L", 128 | fill="#64748B", 129 | font=("ArialMT", 16 * -1) 130 | ) 131 | self.quarterPL = self.canvas.create_text( 132 | 449.0, 133 | 458.0, 134 | anchor="nw", 135 | text="quarter for P/L", 136 | fill="#64748B", 137 | font=("ArialMT", 16 * -1) 138 | ) 139 | self.yearPL = self.canvas.create_text( 140 | 447.0, 141 | 503.0, 142 | anchor="nw", 143 | text="year for P/L", 144 | fill="#64748B", 145 | font=("ArialMT", 16 * -1) 146 | ) 147 | 148 | self.canvas.pack(fill="both", expand=True) 149 | 150 | self.canvas.bind("", self.frame_clicked) 151 | 152 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 153 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 154 | 155 | def frame_clicked(self, event): 156 | x = event.x 157 | y = event.y 158 | # print(f"{self.name} clicked, x: {x} y: {y}") 159 | if x <= 200: 160 | # Sidebar area clicked 161 | # print("Sidebar clicked, frame0") 162 | self.parent.sidebar_clicked(x, y) 163 | elif 200 <= x <= 1096 and y <= 60: 164 | # Top bar area clicked 165 | # print("Top_bar clicked, frame0") 166 | self.parent.top_bar_clicked(x, y) 167 | else: 168 | # frame area clicked 169 | pass 170 | 171 | def update_data(self): 172 | self.update_market_status() 173 | self.parent.trader.update_account_info() 174 | self.calculate_performance() 175 | self.set_total_PL(' - ') 176 | self.set_openPL(f'{self.parent.trader.openPL} {self.parent.trader.openPL_pct}') 177 | self.set_day_PL(f'{self.parent.trader.dayPL} {self.parent.trader.dayPL_pct}') 178 | self.set_cash_balance(self.parent.trader.cash_balance) 179 | self.set_market_value(self.parent.trader.market_value) 180 | self.set_weekPL(' - ') 181 | self.set_monthPL(' - ') 182 | self.set_quarterPL(' - ') 183 | self.set_yearPL(' - ') 184 | 185 | def msg_clicked(self, event): 186 | # print(f"{self.name}: Message clicked") 187 | # Not core functionality, implement later 188 | pass 189 | def notify_clicked(self, event): 190 | # print(f"{self.name}: Notify clicked") 191 | # Not core functionality, implement later 192 | pass 193 | 194 | def calculate_performance(self): 195 | # TODO: calculate performance 196 | pass 197 | 198 | def update_market_status(self): 199 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 200 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 201 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 202 | 203 | def set_total_PL(self, value): 204 | self.canvas.itemconfig(self.total_PL, text=value) 205 | 206 | def set_day_PL(self, value): 207 | self.canvas.itemconfig(self.day_PL, text=value) 208 | 209 | def set_openPL(self, value): 210 | self.canvas.itemconfig(self.openPL, text=value) 211 | 212 | def set_cash_balance(self, value): 213 | self.canvas.itemconfig(self.cash_balance, text=value) 214 | 215 | def set_market_value(self, value): 216 | self.canvas.itemconfig(self.market_value, text=value) 217 | 218 | def set_weekPL(self, value): 219 | self.canvas.itemconfig(self.weekPL, text=value) 220 | 221 | def set_monthPL(self, value): 222 | self.canvas.itemconfig(self.monthPL, text=value) 223 | 224 | def set_quarterPL(self, value): 225 | self.canvas.itemconfig(self.quarterPL, text=value) 226 | 227 | def set_yearPL(self, value): 228 | self.canvas.itemconfig(self.yearPL, text=value) 229 | 230 | -------------------------------------------------------------------------------- /gui/gui_5_Performance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/__init__.py -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_1.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_2.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_3.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_4.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_5.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_6.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_7.png -------------------------------------------------------------------------------- /gui/gui_5_Performance/frame5/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_5_Performance/frame5/image_8.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/APPLog.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, Text, PhotoImage 4 | 5 | from utils.dataIO import read_log_msg 6 | 7 | 8 | class APPLog(tk.Frame): 9 | def __init__(self, parent): 10 | # Initialize the frame 11 | tk.Frame.__init__(self, parent) 12 | self.name = "APPLog" 13 | self.parent = parent 14 | self.current = False 15 | 16 | # UI elements: 17 | OUTPUT_PATH = Path(__file__).parent 18 | ASSETS_PATH = OUTPUT_PATH / Path(r"./frame6") 19 | 20 | def relative_to_assets(path: str) -> Path: 21 | return ASSETS_PATH / Path(path) 22 | 23 | self.canvas = Canvas( 24 | self, 25 | bg="#F1F5F9", 26 | height=728, 27 | width=1096, 28 | bd=0, 29 | highlightthickness=0, 30 | relief="ridge" 31 | ) 32 | 33 | self.canvas.place(x=0, y=0) 34 | 35 | self.image_1 = PhotoImage(file=relative_to_assets("image_1.png")) 36 | self.image_image_1 = self.canvas.create_image(548.0, 364.0, image=self.image_1) 37 | 38 | self.image_2 = PhotoImage(file=relative_to_assets("image_2.png")) 39 | self.image_image_2 = self.canvas.create_image(648.0, 385.0, image=self.image_2) 40 | 41 | self.image_3 = PhotoImage(file=relative_to_assets("image_3.png")) 42 | self.image_3_msg = self.canvas.create_image(1012.0, 30.0, image=self.image_3) 43 | 44 | self.image_4 = PhotoImage(file=relative_to_assets("image_4.png")) 45 | self.image_4_notify = self.canvas.create_image(971.0, 30.0, image=self.image_4) 46 | 47 | self.image_5 = PhotoImage(file=relative_to_assets("image_5.png")) 48 | self.image_image_5 = self.canvas.create_image(328.0, 30.0, image=self.image_5) 49 | 50 | self.image_6 = PhotoImage(file=relative_to_assets("image_6.png")) 51 | self.image_image_6 = self.canvas.create_image(534.0, 30.0, image=self.image_6) 52 | 53 | self.image_7 = PhotoImage(file=relative_to_assets("image_7.png")) 54 | self.image_image_7 = self.canvas.create_image(534.0, 30.0, image=self.image_7) 55 | 56 | self.image_8 = PhotoImage(file=relative_to_assets("image_8.png")) 57 | self.image_image_8 = self.canvas.create_image(328.0, 30.0, image=self.image_8) 58 | 59 | self.dji = self.canvas.create_text( 60 | 536.0, 61 | 707.0, 62 | anchor="nw", 63 | text="placeholder for dji", 64 | fill="#64748B", 65 | font=("ArialMT", 12 * -1) 66 | ) 67 | 68 | self.spx = self.canvas.create_text( 69 | 273.0, 70 | 707.0, 71 | anchor="nw", 72 | text="placeholder for spx", 73 | fill="#64748B", 74 | font=("ArialMT", 12 * -1) 75 | ) 76 | 77 | self.ixic = self.canvas.create_text( 78 | 404.0, 79 | 707.0, 80 | anchor="nw", 81 | text="placeholder for ndx", 82 | fill="#64748B", 83 | font=("ArialMT", 12 * -1) 84 | ) 85 | 86 | self.entry_image_1 = PhotoImage( 87 | file=relative_to_assets("entry_1.png")) 88 | self.entry_bg_1 = self.canvas.create_image( 89 | 640.0, 90 | 376.0, 91 | image=self.entry_image_1 92 | ) 93 | 94 | self.entry_1_log_file = Text( 95 | self.canvas, 96 | bd=0, 97 | bg="#EFF4FB", 98 | fg="#000716", 99 | highlightthickness=0 100 | ) 101 | self.entry_1_log_file.place( 102 | x=269.0, 103 | y=176.0, 104 | width=742.0, 105 | height=398.0 106 | ) 107 | 108 | self.log_location = self.canvas.create_text( 109 | 275.0, 110 | 605.0, 111 | anchor="nw", 112 | text="Log File Path: app_running.log", 113 | fill="#64748B", 114 | font=("ArialMT", 12 * -1) 115 | ) 116 | 117 | self.canvas.pack(fill="both", expand=True) 118 | 119 | self.canvas.bind("", self.frame_clicked) 120 | 121 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 122 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 123 | 124 | def frame_clicked(self, event): 125 | x = event.x 126 | y = event.y 127 | if x <= 200: 128 | # Sidebar area clicked 129 | # print("Sidebar clicked, frame0") 130 | self.parent.sidebar_clicked(x, y) 131 | elif 200 <= x <= 1096 and y <= 60: 132 | # Top bar area clicked 133 | # print("Top_bar clicked, frame0") 134 | self.parent.top_bar_clicked(x, y) 135 | else: 136 | # frame area clicked 137 | pass 138 | 139 | def update_data(self): 140 | self.update_market_status() 141 | self.set_log_file_area() 142 | 143 | def msg_clicked(self, event): 144 | # print(f"{self.name}: Message clicked") 145 | # not core functionality, implement later 146 | pass 147 | 148 | def notify_clicked(self, event): 149 | # print(f"{self.name}: Notify clicked") 150 | # not core functionality, implement later 151 | pass 152 | 153 | def set_log_file_area(self): 154 | # TODO: read log file 155 | log_file = read_log_msg() 156 | self.entry_1_log_file.delete("1.0", tk.END) 157 | self.entry_1_log_file.insert("1.0", log_file) 158 | # move the cursor to the end of the text 159 | self.entry_1_log_file.see(tk.END) 160 | 161 | def update_market_status(self): 162 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 163 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 164 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 165 | -------------------------------------------------------------------------------- /gui/gui_6_APPLog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/__init__.py -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/entry_1.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_1.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_2.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_3.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_4.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_5.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_6.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_7.png -------------------------------------------------------------------------------- /gui/gui_6_APPLog/frame6/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_6_APPLog/frame6/image_8.png -------------------------------------------------------------------------------- /gui/gui_7_Message/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/__init__.py -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/entry_1.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/entry_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/entry_2.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/entry_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/entry_3.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/entry_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/entry_4.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/entry_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/entry_5.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_1.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_10.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_2.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_3.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_4.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_5.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_6.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_7.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_8.png -------------------------------------------------------------------------------- /gui/gui_7_Message/frame7/image_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_7_Message/frame7/image_9.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/DownloadData.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, Entry, Text, PhotoImage, messagebox 4 | 5 | from utils.dataIO import logging_info, logging_error 6 | from utils.download_max_history_candles import download_max_history_candles 7 | from utils.update_intraday_data_history import update_intraday_data_history 8 | 9 | 10 | class DownloadData(tk.Frame): 11 | 12 | def __init__(self, parent): 13 | # Initialize the frame 14 | tk.Frame.__init__(self, parent) 15 | self.name = "DownloadData" 16 | self.parent = parent 17 | self.current = False 18 | 19 | # UI elements: 20 | OUTPUT_PATH = Path(__file__).parent 21 | ASSETS_PATH = OUTPUT_PATH / Path(r"./frame8") 22 | 23 | def relative_to_assets(path: str) -> Path: 24 | return ASSETS_PATH / Path(path) 25 | 26 | self.canvas = Canvas( 27 | self, 28 | bg="#F1F5F9", 29 | height=728, 30 | width=1096, 31 | bd=0, 32 | highlightthickness=0, 33 | relief="ridge" 34 | ) 35 | 36 | self.canvas.place(x=0, y=0) 37 | self.image_image_1 = PhotoImage( 38 | file=relative_to_assets("image_1.png")) 39 | self.image_1 = self.canvas.create_image( 40 | 548.0, 41 | 364.0, 42 | image=self.image_image_1 43 | ) 44 | 45 | self.image_image_2 = PhotoImage( 46 | file=relative_to_assets("image_2.png")) 47 | self.image_2 = self.canvas.create_image( 48 | 648.0, 49 | 385.0, 50 | image=self.image_image_2 51 | ) 52 | 53 | self.image_image_3 = PhotoImage( 54 | file=relative_to_assets("image_3.png")) 55 | self.image_3_msg = self.canvas.create_image( 56 | 1012.0, 57 | 30.0, 58 | image=self.image_image_3 59 | ) 60 | 61 | self.image_image_4 = PhotoImage( 62 | file=relative_to_assets("image_4.png")) 63 | self.image_4_notify = self.canvas.create_image( 64 | 971.0, 65 | 30.0, 66 | image=self.image_image_4 67 | ) 68 | 69 | self.image_image_5 = PhotoImage( 70 | file=relative_to_assets("image_5.png")) 71 | self.image_5 = self.canvas.create_image( 72 | 328.0, 73 | 30.0, 74 | image=self.image_image_5 75 | ) 76 | 77 | self.image_image_6 = PhotoImage( 78 | file=relative_to_assets("image_6.png")) 79 | self.image_6 = self.canvas.create_image( 80 | 534.0, 81 | 30.0, 82 | image=self.image_image_6 83 | ) 84 | 85 | self.image_image_7 = PhotoImage( 86 | file=relative_to_assets("image_7.png")) 87 | self.image_7 = self.canvas.create_image( 88 | 534.0, 89 | 30.0, 90 | image=self.image_image_7 91 | ) 92 | 93 | self.image_image_8 = PhotoImage( 94 | file=relative_to_assets("image_8.png")) 95 | self.image_8 = self.canvas.create_image( 96 | 328.0, 97 | 30.0, 98 | image=self.image_image_8 99 | ) 100 | 101 | self.dji = self.canvas.create_text( 102 | 536.0, 103 | 707.0, 104 | anchor="nw", 105 | text="placeholder for dji", 106 | fill="#64748B", 107 | font=("ArialMT", 12 * -1) 108 | ) 109 | 110 | self.spx = self.canvas.create_text( 111 | 273.0, 112 | 707.0, 113 | anchor="nw", 114 | text="placeholder for spx", 115 | fill="#64748B", 116 | font=("ArialMT", 12 * -1) 117 | ) 118 | 119 | self.ixic = self.canvas.create_text( 120 | 404.0, 121 | 707.0, 122 | anchor="nw", 123 | text="placeholder for ndx", 124 | fill="#64748B", 125 | font=("ArialMT", 12 * -1) 126 | ) 127 | 128 | self.entry_image_1 = PhotoImage( 129 | file=relative_to_assets("entry_1.png")) 130 | self.entry_bg_1 = self.canvas.create_image( 131 | 491.0, 132 | 193.5, 133 | image=self.entry_image_1 134 | ) 135 | self.entry_1_ticker_intraday = Entry( 136 | self.canvas, 137 | bd=0, 138 | bg="#FFFFFF", 139 | fg="#000716", 140 | highlightthickness=0 141 | ) 142 | self.entry_1_ticker_intraday.place( 143 | x=395.0, 144 | y=181.0, 145 | width=192.0, 146 | height=23.0 147 | ) 148 | 149 | self.entry_image_2 = PhotoImage( 150 | file=relative_to_assets("entry_2.png")) 151 | self.entry_bg_2 = self.canvas.create_image( 152 | 492.0, 153 | 414.5, 154 | image=self.entry_image_2 155 | ) 156 | self.entry_1_ticker_history = Entry( 157 | self.canvas, 158 | bd=0, 159 | bg="#FFFFFF", 160 | fg="#000716", 161 | highlightthickness=0 162 | ) 163 | self.entry_1_ticker_history.place( 164 | x=396.0, 165 | y=402.0, 166 | width=192.0, 167 | height=23.0 168 | ) 169 | 170 | self.entry_image_3 = PhotoImage( 171 | file=relative_to_assets("entry_3.png")) 172 | self.entry_bg_3 = self.canvas.create_image( 173 | 840.0, 174 | 380.0, 175 | image=self.entry_image_3 176 | ) 177 | self.entry_3_future_use = Text( 178 | self.canvas, 179 | bd=0, 180 | bg="#EFF4FB", 181 | fg="#000716", 182 | highlightthickness=0 183 | ) 184 | self.entry_3_future_use.place( 185 | x=669.0, 186 | y=180.0, 187 | width=342.0, 188 | height=398.0 189 | ) 190 | 191 | self.canvas.pack(fill="both", expand=True) 192 | 193 | self.canvas.bind("", self.frame_clicked) 194 | 195 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 196 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 197 | 198 | def frame_clicked(self, event): 199 | x = event.x 200 | y = event.y 201 | if x <= 200: 202 | # Sidebar area clicked 203 | self.parent.sidebar_clicked(x, y) 204 | elif 200 <= x <= 1096 and y <= 60: 205 | # Top bar area clicked 206 | self.parent.top_bar_clicked(x, y) 207 | else: 208 | # frame area clicked 209 | if 378 <= x <= 502 and 304 <= y <= 334: 210 | self.download_intraday_clicked() 211 | elif 378 <= x <= 502 and 501 <= y <= 531: 212 | self.download_max_history_clicked() 213 | 214 | def update_data(self): 215 | self.update_market_status() 216 | 217 | def download_intraday_clicked(self): 218 | ticker_input = self.entry_1_ticker_intraday.get() 219 | if ticker_input == "": 220 | messagebox.showinfo("Oops something went wrong", "Please enter the ticker") 221 | else: 222 | stock_list = ticker_input.split(" ") 223 | try: 224 | print('Downloading intraday data...') 225 | for stock in stock_list: 226 | update_intraday_data_history(stock) 227 | self.parent.show_info_message("Intraday data downloaded successfully") 228 | logging_info(f"Intraday data downloaded successfully: {ticker_input}") 229 | except Exception as e: 230 | print(e) 231 | messagebox.showinfo("Oops something went wrong", "Please separate tickers with a space") 232 | 233 | def download_max_history_clicked(self): 234 | ticker_input = self.entry_1_ticker_history.get() 235 | if ticker_input == "": 236 | messagebox.showinfo("Oops something went wrong", "Please enter the ticker") 237 | else: 238 | stock_list = ticker_input.split(" ") 239 | try: 240 | print('Downloading history data...') 241 | for stock in stock_list: 242 | download_max_history_candles(stock) 243 | self.parent.show_info_message("History data downloaded successfully") 244 | logging_info(f"History data downloaded successfully: {ticker_input}") 245 | except Exception as e: 246 | # print(e) 247 | logging_error(str(e)) 248 | messagebox.showinfo("Oops something went wrong", "Please separate tickers with a space") 249 | 250 | def msg_clicked(self, event): 251 | # print(f"{self.name}: Message clicked") 252 | # not core functionality, implement later 253 | pass 254 | 255 | def notify_clicked(self, event): 256 | # print(f"{self.name}: Notify clicked") 257 | # not core functionality, implement later 258 | pass 259 | 260 | def update_market_status(self): 261 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 262 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 263 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 264 | -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/__init__.py -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/entry_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/entry_1.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/entry_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/entry_2.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/entry_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/entry_3.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_1.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_2.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_3.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_4.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_5.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_6.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_7.png -------------------------------------------------------------------------------- /gui/gui_8_DownloadData/frame8/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_8_DownloadData/frame8/image_8.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/SaveExit.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Canvas, PhotoImage 4 | 5 | from utils.dataIO import logging_info 6 | 7 | 8 | class SaveExit(tk.Frame): 9 | 10 | def __init__(self, parent): 11 | # Initialize the frame 12 | tk.Frame.__init__(self, parent) 13 | self.name = "SaveExit" 14 | self.parent = parent 15 | self.current = False 16 | 17 | # UI elements: 18 | OUTPUT_PATH = Path(__file__).parent 19 | ASSETS_PATH = OUTPUT_PATH / Path(r"./frame9") 20 | 21 | def relative_to_assets(path: str) -> Path: 22 | return ASSETS_PATH / Path(path) 23 | 24 | self.canvas = Canvas( 25 | self, 26 | bg="#F1F5F9", 27 | height=728, 28 | width=1096, 29 | bd=0, 30 | highlightthickness=0, 31 | relief="ridge" 32 | ) 33 | 34 | self.canvas.place(x=0, y=0) 35 | self.image_image_1 = PhotoImage( 36 | file=relative_to_assets("image_1.png")) 37 | self.image_1 = self.canvas.create_image( 38 | 548.0, 39 | 364.0, 40 | image=self.image_image_1 41 | ) 42 | 43 | self.image_image_2 = PhotoImage( 44 | file=relative_to_assets("image_2.png")) 45 | self.image_2 = self.canvas.create_image( 46 | 648.0, 47 | 385.0, 48 | image=self.image_image_2 49 | ) 50 | 51 | self.image_image_3 = PhotoImage( 52 | file=relative_to_assets("image_3.png")) 53 | self.image_3_msg = self.canvas.create_image( 54 | 1012.0, 55 | 30.0, 56 | image=self.image_image_3 57 | ) 58 | 59 | self.image_image_4 = PhotoImage( 60 | file=relative_to_assets("image_4.png")) 61 | self.image_4_notify = self.canvas.create_image( 62 | 971.0, 63 | 30.0, 64 | image=self.image_image_4 65 | ) 66 | 67 | self.image_image_5 = PhotoImage( 68 | file=relative_to_assets("image_5.png")) 69 | self.image_5 = self.canvas.create_image( 70 | 328.0, 71 | 30.0, 72 | image=self.image_image_5 73 | ) 74 | 75 | self.image_image_6 = PhotoImage( 76 | file=relative_to_assets("image_6.png")) 77 | self.image_6 = self.canvas.create_image( 78 | 534.0, 79 | 30.0, 80 | image=self.image_image_6 81 | ) 82 | 83 | self.image_image_7 = PhotoImage( 84 | file=relative_to_assets("image_7.png")) 85 | self.image_7 = self.canvas.create_image( 86 | 534.0, 87 | 30.0, 88 | image=self.image_image_7 89 | ) 90 | 91 | self.image_image_8 = PhotoImage( 92 | file=relative_to_assets("image_8.png")) 93 | self.image_8 = self.canvas.create_image( 94 | 328.0, 95 | 30.0, 96 | image=self.image_image_8 97 | ) 98 | 99 | self.dji = self.canvas.create_text( 100 | 536.0, 101 | 707.0, 102 | anchor="nw", 103 | text="placeholder for dji", 104 | fill="#64748B", 105 | font=("ArialMT", 12 * -1) 106 | ) 107 | 108 | self.spx = self.canvas.create_text( 109 | 273.0, 110 | 707.0, 111 | anchor="nw", 112 | text="placeholder for spx", 113 | fill="#64748B", 114 | font=("ArialMT", 12 * -1) 115 | ) 116 | 117 | self.ixic = self.canvas.create_text( 118 | 404.0, 119 | 707.0, 120 | anchor="nw", 121 | text="placeholder for ndx", 122 | fill="#64748B", 123 | font=("ArialMT", 12 * -1) 124 | ) 125 | 126 | self.canvas.create_text( 127 | 706.0, 128 | 342.0, 129 | anchor="nw", 130 | text="seconds", 131 | fill="#004434", 132 | font=("ArialRoundedMTBold", 16 * -1) 133 | ) 134 | 135 | self.canvas.create_text( 136 | 466.0, 137 | 342.0, 138 | anchor="nw", 139 | text="APP State Saved, will exit in ", 140 | fill="#004434", 141 | font=("ArialRoundedMTBold", 16 * -1) 142 | ) 143 | 144 | self.seconds_count_down = self.canvas.create_text( 145 | 680.0, 146 | 342.0, 147 | anchor="nw", 148 | text="3", 149 | fill="#004434", 150 | font=("ArialRoundedMTBold", 16 * -1) 151 | ) 152 | 153 | self.canvas.pack(fill="both", expand=True) 154 | 155 | self.canvas.bind("", self.frame_clicked) 156 | 157 | self.canvas.tag_bind(self.image_3_msg, "", self.msg_clicked) 158 | self.canvas.tag_bind(self.image_4_notify, "", self.notify_clicked) 159 | 160 | def frame_clicked(self, event): 161 | x = event.x 162 | y = event.y 163 | print(f"{self.name} clicked, x: {x} y: {y}") 164 | if x <= 200: 165 | # Sidebar area clicked 166 | print("Sidebar clicked, frame0") 167 | self.parent.sidebar_clicked(x, y) 168 | elif 200 <= x <= 1096 and y <= 60: 169 | # Top bar area clicked 170 | print("Top_bar clicked, frame0") 171 | self.parent.top_bar_clicked(x, y) 172 | else: 173 | # frame area clicked 174 | pass 175 | 176 | def update_data(self): 177 | self.update_market_status() 178 | self.save_exit() 179 | 180 | def msg_clicked(self, event): 181 | # print(f"{self.name}: Message clicked") 182 | # not core functionality, implement later 183 | pass 184 | 185 | def notify_clicked(self, event): 186 | # print(f"{self.name}: Notify clicked") 187 | # not core functionality, implement later 188 | pass 189 | 190 | def save_exit(self): 191 | def update_countdown(count): 192 | self.canvas.itemconfig(self.seconds_count_down, text=count) # Update label text with current count 193 | if count >= 0: 194 | self.parent.after(1000, update_countdown, count - 1) # Schedule next update after 1 second 195 | else: 196 | # Countdown finished 197 | logging_info("Exiting app") 198 | self.parent.exit_app() 199 | 200 | # Start the countdown from 3 201 | update_countdown(3) 202 | 203 | def update_seconds_count_down(self, seconds): 204 | self.canvas.itemconfig(self.seconds_count_down, text=seconds) 205 | 206 | def update_market_status(self): 207 | self.canvas.itemconfig(self.spx, text=self.parent.spx_price) 208 | self.canvas.itemconfig(self.dji, text=self.parent.dji_price) 209 | self.canvas.itemconfig(self.ixic, text=self.parent.ixic_price) 210 | -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/__init__.py -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_1.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_2.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_3.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_4.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_5.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_6.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_7.png -------------------------------------------------------------------------------- /gui/gui_9_SaveExit/frame9/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/gui_9_SaveExit/frame9/image_8.png -------------------------------------------------------------------------------- /gui/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/icon.ico -------------------------------------------------------------------------------- /gui/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/icon.png -------------------------------------------------------------------------------- /gui/trading.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/trading.ico -------------------------------------------------------------------------------- /gui/trading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/gui/trading.png -------------------------------------------------------------------------------- /image/bkg_tmp1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/image/bkg_tmp1.jpg -------------------------------------------------------------------------------- /image/bkg_tmp2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/image/bkg_tmp2.jpg -------------------------------------------------------------------------------- /quoter/Quoter.py: -------------------------------------------------------------------------------- 1 | # This file defines the Quoter class, which is the base class for all quoters. 2 | import yfinance as yf 3 | 4 | 5 | def get_market_index_real_time_price(): 6 | symbols = ["^IXIC", "^GSPC", "^DJI"] # Nasdaq, S&P500, Dow Jones 7 | 8 | indices = yf.Tickers(symbols) 9 | latest_prices = {} 10 | 11 | for symbol in symbols: 12 | index = indices.tickers[symbol] 13 | latest_data = index.history(period="1d") 14 | 15 | if not latest_data.empty: 16 | latest_price = latest_data["Close"].iloc[-1] 17 | latest_prices[symbol] = latest_price 18 | else: 19 | latest_prices[symbol] = None 20 | 21 | return latest_prices 22 | 23 | 24 | class Quoter: 25 | def __init__(self): 26 | self.name = "Quoter" 27 | 28 | """ 29 | 30 | Yahoo: 31 | Valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo; (Intraday data cannot extend last 60 days) 32 | Valid count/period: 33 | 1m: 5d 34 | 2m,5m,15m,30m: 1mo 35 | 1h: <2y 36 | d1 and above: 10y 37 | 38 | Set as default quoter in strategy, fast and stable. 39 | 40 | 41 | Webull: 42 | Valid intervals: m1, m5, m15, m30, d1; (h1, h2, h4, w1 are currently not working) 43 | Valid count/period: 44 | m1: 1200 45 | m5: 1200 46 | m15: 1200 47 | m30: 1049 48 | d1: 1200 49 | interval : str 50 | 51 | Set as default quoter in trade/order related, real time ask and bid price. 52 | """ 53 | 54 | def get_current_quote(self, stock): 55 | pass 56 | 57 | def get_1min_bar(self, stock, count='max', extend_trading=False): 58 | pass 59 | 60 | def get_2min_bar(self, stock, count='max', extend_trading=False): 61 | pass 62 | 63 | def get_3min_bar(self, stock, count='max', extend_trading=False): 64 | pass 65 | 66 | def get_5min_bar(self, stock, count='max', extend_trading=False): 67 | pass 68 | 69 | def get_15min_bar(self, stock, count='max', extend_trading=False): 70 | pass 71 | 72 | def get_30min_bar(self, stock, count='max', extend_trading=False): 73 | pass 74 | 75 | def get_1h_bar(self, stock, count='max', extend_trading=False): 76 | pass 77 | 78 | def get_2h_bar(self, stock, count='max', extend_trading=False): 79 | pass 80 | 81 | def get_4h_bar(self, stock, count='max', extend_trading=False): 82 | pass 83 | 84 | def get_1d_bar(self, stock, count='max', extend_trading=False): 85 | pass 86 | 87 | def get_1w_bar(self, stock, count='max', extend_trading=False): 88 | pass 89 | -------------------------------------------------------------------------------- /quoter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/quoter/__init__.py -------------------------------------------------------------------------------- /quoter/quoter_Webull.py: -------------------------------------------------------------------------------- 1 | from webull import webull 2 | 3 | from quoter.Quoter import Quoter 4 | from utils.dataIO import logging_info, logging_error, logging_warning 5 | 6 | 7 | class Quoter_Webull(Quoter): 8 | 9 | def __init__(self): 10 | super().__init__() 11 | self.name = "Webull Platform" 12 | self.wb = webull() 13 | logging_info('Quoter_Webull created') 14 | 15 | """ 16 | response time: around 400 - 800 ms 17 | Webull: 18 | Valid intervals: m1, m5, m15, m30, d1; (h1, h2, h4, w1 are currently not working) 19 | max_count: 20 | m1: 1200 21 | m5: 1200 22 | m15: 1200 23 | m30: 1049 24 | d1: 1200 25 | interval : str 26 | return: pandas dataframe 27 | """ 28 | 29 | def get_current_quote(self, stock): 30 | # best to call in Trader class 31 | try: 32 | response = self.wb.get_quote(stock) 33 | ask_price_1 = response['askList'][0]['price'] 34 | bid_price_1 = response['bidList'][0]['price'] 35 | res = (bid_price_1, ask_price_1) 36 | logging_info('Get current quote') 37 | return res 38 | except KeyError: 39 | logging_error('Please login to get the real time quote') 40 | return None 41 | 42 | def get_1min_bar(self, stock, count='max', extend_trading=False): 43 | # TODO: need to check if the count is valid, same for others 44 | cnt = 1200 if count == 'max' else count 45 | extend = 1 if extend_trading else 0 46 | response = self.wb.get_bars(stock, interval='m1', count=cnt, extendTrading=extend) 47 | if len(response) == cnt and len(response) == 1: 48 | return response 49 | else: 50 | logging_warning('Quote count exceed server max count') 51 | return None 52 | 53 | def get_5min_bar(self, stock, count='max', extend_trading=False): 54 | cnt = 1200 if count == 'max' else count 55 | extend = 1 if extend_trading else 0 56 | response = self.wb.get_bars(stock, interval='m5', count=cnt, extendTrading=extend) 57 | if not len(response) == cnt and len(response) == 1: 58 | response = None 59 | logging_warning('Quote count exceed server max count') 60 | return response 61 | 62 | def get_15min_bar(self, stock, count='max', extend_trading=False): 63 | cnt = 1200 if count == 'max' else count 64 | extend = 1 if extend_trading else 0 65 | response = self.wb.get_bars(stock, interval='m15', count=cnt, extendTrading=extend) 66 | if not len(response) == cnt and len(response) == 1: 67 | response = None 68 | logging_warning('Quote count exceed server max count') 69 | return response 70 | 71 | def get_30min_bar(self, stock, count='max', extend_trading=False): 72 | cnt = 1049 if count == 'max' else count 73 | extend = 1 if extend_trading else 0 74 | response = self.wb.get_bars(stock, interval='m30', count=cnt, extendTrading=extend) 75 | if not len(response) == cnt and len(response) == 1: 76 | response = None 77 | logging_warning('Quote count exceed server max count') 78 | return response 79 | 80 | def get_1d_bar(self, stock, count='max', extend_trading=False): 81 | cnt = 1200 if count == 'max' else count 82 | extend = 1 if extend_trading else 0 83 | response = self.wb.get_bars(stock, interval='d1', count=cnt, extendTrading=extend) 84 | if not len(response) == cnt and len(response) == 1: 85 | response = None 86 | logging_warning('Quote count exceed server max count') 87 | return response 88 | 89 | def get_1w_bar(self, stock, count='max', extend_trading=False): 90 | cnt = 1200 if count == 'max' else count 91 | extend = 1 if extend_trading else 0 92 | response = self.wb.get_bars(stock, interval='w1', count=cnt, extendTrading=extend) 93 | if not len(response) == cnt and len(response) == 1: 94 | response = None 95 | logging_warning('Quote count exceed server max count') 96 | return response 97 | -------------------------------------------------------------------------------- /quoter/quoter_Yahoo.py: -------------------------------------------------------------------------------- 1 | import yfinance as yf 2 | 3 | from quoter.Quoter import Quoter 4 | from utils.dataIO import logging_info, logging_error 5 | 6 | 7 | class Quoter_Yahoo(Quoter): 8 | def __init__(self): 9 | super().__init__() 10 | self.name = "Yahoo Platform" 11 | logging_info('Quoter_Yahoo created') 12 | 13 | """ 14 | Limit: a maximum of 10 requests per second per userw 15 | 2,000 requests per hour per IP (or up to a total of 48,000 requests a day) 16 | 17 | Response time: around 200 - 400 ms 18 | 19 | Yahoo: 20 | interval : str 21 | Yahoo: 22 | Valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo; (Intraday data cannot extend last 60 days) 23 | Valid count/period: 24 | 1m: 5d 25 | 2m,5m,15m,30m: 1mo 26 | 1h: <2y 27 | d1 and above: 10y 28 | 29 | Set as default quoter in strategy, fast and stable. 30 | 31 | Reference: 32 | https://github.com/ranaroussi/yfinance 33 | https://aroussi.com/post/python-yahoo-finance 34 | """ 35 | 36 | def get_1min_bar(self, stock, count='max', extend_trading=False): 37 | """ 38 | :param stock: str or list 39 | :param count: number of bars 40 | :param extend_trading: including before and after trading 41 | :return: pandas dataframe or dict of dfs 42 | Same to all other get_bar functions 43 | """ 44 | if len(stock) == 0: 45 | logging_error('stock cannot be empty') 46 | return None 47 | 48 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 49 | res_df = yf.Ticker(stock).history(period='5d', interval='1m', actions=False, prepost=extend_trading) 50 | 51 | return res_df 52 | 53 | elif type(stock) == list: 54 | tickers = yf.Tickers(stock) 55 | df_tickers = tickers.history(period='5d', interval='1m', actions=False, 56 | prepost=extend_trading, group_by="ticker") 57 | res_dict = {} 58 | for i in stock: 59 | res_dict[i] = df_tickers[i] 60 | 61 | return res_dict 62 | 63 | def get_2min_bar(self, stock, count='max', extend_trading=False): 64 | if len(stock) == 0: 65 | logging_error('stock cannot be empty') 66 | return None 67 | 68 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 69 | res_df = yf.Ticker(stock).history(interval='2m', actions=False, prepost=extend_trading) 70 | 71 | return res_df 72 | 73 | elif type(stock) == list: 74 | tickers = yf.Tickers(stock) 75 | df_tickers = tickers.history(interval='2m', actions=False, 76 | prepost=extend_trading, group_by="ticker") 77 | res_dict = {} 78 | for i in stock: 79 | res_dict[i] = df_tickers[i] 80 | 81 | return res_dict 82 | 83 | def get_5min_bar(self, stock, count='max', extend_trading=False): 84 | if len(stock) == 0: 85 | logging_error('stock cannot be empty') 86 | return None 87 | 88 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 89 | res_df = yf.Ticker(stock).history(interval='5m', actions=False, prepost=extend_trading) 90 | 91 | return res_df 92 | 93 | elif type(stock) == list: 94 | tickers = yf.Tickers(stock) 95 | df_tickers = tickers.history(interval='5m', actions=False, 96 | prepost=extend_trading, group_by="ticker") 97 | res_dict = {} 98 | for i in stock: 99 | res_dict[i] = df_tickers[i] 100 | 101 | return res_dict 102 | 103 | def get_15min_bar(self, stock, count='max', extend_trading=False): 104 | if len(stock) == 0: 105 | logging_error('stock cannot be empty') 106 | return None 107 | 108 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 109 | res_df = yf.Ticker(stock).history(interval='15m', actions=False, prepost=extend_trading) 110 | 111 | return res_df 112 | 113 | elif type(stock) == list: 114 | tickers = yf.Tickers(stock) 115 | df_tickers = tickers.history(interval='15m', actions=False, 116 | prepost=extend_trading, group_by="ticker") 117 | res_dict = {} 118 | for i in stock: 119 | res_dict[i] = df_tickers[i] 120 | 121 | return res_dict 122 | 123 | def get_30min_bar(self, stock, count='max', extend_trading=False): 124 | if len(stock) == 0: 125 | logging_error('stock cannot be empty') 126 | return False 127 | 128 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 129 | res_df = yf.Ticker(stock).history(interval='30m', actions=False, prepost=extend_trading) 130 | 131 | return res_df 132 | 133 | elif type(stock) == list: 134 | tickers = yf.Tickers(stock) 135 | df_tickers = tickers.history(interval='30m', actions=False, 136 | prepost=extend_trading, group_by="ticker") 137 | res_dict = {} 138 | for i in stock: 139 | res_dict[i] = df_tickers[i] 140 | 141 | return res_dict 142 | 143 | def get_1h_bar(self, stock, count='max', extend_trading=False): 144 | if len(stock) == 0: 145 | logging_error('stock cannot be empty') 146 | return None 147 | if count == 'max': 148 | period = '1y' 149 | else: 150 | # specific time period current not working 151 | pass 152 | 153 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 154 | res_df = yf.Ticker(stock).history(period=period, interval='1h', actions=False, prepost=extend_trading) 155 | 156 | return res_df 157 | 158 | elif type(stock) == list: 159 | tickers = yf.Tickers(stock) 160 | df_tickers = tickers.history(period=period, interval='1h', actions=False, 161 | prepost=extend_trading, group_by="ticker") 162 | res_dict = {} 163 | for i in stock: 164 | res_dict[i] = df_tickers[i] 165 | 166 | return res_dict 167 | 168 | def get_1d_bar(self, stock, count='max', extend_trading=False): 169 | if len(stock) == 0: 170 | logging_error('stock cannot be empty') 171 | return None 172 | if count == 'max': 173 | period = '10y' 174 | else: 175 | # specific time period current not working, using period instead 176 | period = count 177 | 178 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 179 | res_df = yf.Ticker(stock).history(period=period, interval='1d', actions=False, prepost=extend_trading) 180 | 181 | return res_df 182 | 183 | elif type(stock) == list: 184 | tickers = yf.Tickers(stock) 185 | df_tickers = tickers.history(period=period, interval='1d', actions=False, 186 | prepost=extend_trading, group_by="ticker") 187 | res_dict = {} 188 | for i in stock: 189 | res_dict[i] = df_tickers[i] 190 | 191 | return res_dict 192 | 193 | def get_1w_bar(self, stock, count='max', extend_trading=False): 194 | if len(stock) == 0: 195 | logging_error('stock cannot be empty') 196 | return None 197 | if count == 'max': 198 | period = '10y' 199 | else: 200 | # specific time period current not working, using period instead 201 | period = count 202 | 203 | if type(stock) == str or (type(stock) == list and len(stock) == 1): 204 | res_df = yf.Ticker(stock).history(period=period, interval='1wk', actions=False, prepost=extend_trading) 205 | 206 | return res_df 207 | 208 | elif type(stock) == list: 209 | tickers = yf.Tickers(stock) 210 | df_tickers = tickers.history(period=period, interval='1wk', actions=False, 211 | prepost=extend_trading, group_by="ticker") 212 | res_dict = {} 213 | for i in stock: 214 | res_dict[i] = df_tickers[i] 215 | 216 | return res_dict 217 | 218 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests~=2.31.0 2 | numpy~=1.26.4 3 | pandas~=2.2.2 4 | git+https://github.com/twopirllc/pandas-ta 5 | schedule~=1.2.1 6 | simpleaudio~=1.0.4 7 | twilio~=9.0.5 8 | yfinance~=0.2.38 9 | webull~=0.6.1 10 | discord~=2.3.2 11 | python-dotenv~=1.0.1 12 | -------------------------------------------------------------------------------- /server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/server/__init__.py -------------------------------------------------------------------------------- /strategy/My_Strategy.py: -------------------------------------------------------------------------------- 1 | from strategy.Strategy import Strategy 2 | import pandas_ta as pta 3 | from utils import play_sound 4 | from utils.dataIO import is_market_hours 5 | 6 | 7 | class My_Strategy(Strategy): 8 | """ 9 | Class Name must be the same as the file name 10 | """ 11 | 12 | def __init__(self): 13 | super().__init__() 14 | self.strategy_name = 'My_Strategy' 15 | self.cash_balance = 0 16 | self.market_value = 0 17 | self.dayBuyingPower = 0 18 | self.overnightBuyingPower = 0 19 | self.net_account_value = 0 20 | 21 | """ 22 | You only need to define the strategy_decision() function, and click run strategy button on the App. 23 | Other functions are defined in the parent class, Strategy.py, you can use them directly. 24 | Define your own strategy here: 25 | """ 26 | 27 | def strategy_decision(self): 28 | """This is a simple example strategy from chatGPT, you can overwrite it in your own strategy""" 29 | # You can extract the code to several functions, and call them in strategy_decision() 30 | 31 | # ------------------------- 32 | # Strategy starts here: 33 | # 0. define the stock ticker you are going to trade 34 | stock = 'TQQQ' 35 | 36 | # 1. get the stock data from quoter, return a pandas dataframe 37 | data = self.quoter.get_15min_bar(stock=stock, extend_trading=True) 38 | 39 | # 2. calculate the indicator 40 | data['RSI'] = pta.rsi(data['Close'], length=14) 41 | 42 | # 3. check the strategy condition, 43 | # for example, simply buy when rsi < 30, sell when rsi > 70 44 | current_rsi = data['RSI'][-1] 45 | action = None 46 | if current_rsi < 30: 47 | # print for your reference 48 | print(f"{stock}: current_rsi: {current_rsi}, price: {data['Close'][-1]}") 49 | print('buy point start') 50 | print('-------------------------') 51 | action = 'buy' 52 | 53 | elif current_rsi > 70: 54 | print(f"{stock}: current_rsi: {current_rsi}, price: {data['Close'][-1]}") 55 | print('sell point start') 56 | print('-------------------------') 57 | action = 'sell' 58 | 59 | else: 60 | pass 61 | 62 | # 4. place order if needed, using the trader object to place order 63 | action_res = {} 64 | if action == 'buy': 65 | if is_market_hours(): 66 | action_res = self.parent.trader.order_market_buy(stock, quantity=1) 67 | else: 68 | price = self.parent.trader.get_bid_price(stock) 69 | action_res = self.parent.trader.order_limit_buy_day(stock, price, quantity=1) 70 | # for GTC order, use self.parent.trader.order_limit_buy_gtc(stock, price, qty) 71 | 72 | play_sound.order_placed() 73 | 74 | elif action == 'sell': 75 | if is_market_hours(): 76 | action_res = self.parent.trader.order_market_sell(stock, quantity=1) 77 | else: 78 | price = self.parent.trader.get_ask_price(stock) 79 | action_res = self.parent.trader.order_limit_sell_gtc(stock, price, quantity=1) 80 | 81 | play_sound.order_placed() 82 | else: 83 | # do whatever you want 84 | pass 85 | 86 | # Strategy ends here 87 | # ----------------------------- 88 | 89 | # Set the info you need to show in the GUI 90 | info = f"{data.index[-1].strftime('%Y-%m-%d %H:%M:%S')}, 15min bar\n" \ 91 | f"Stock: {stock}\n" \ 92 | f"Price: {round(data['Close'][-1], 2)}\n" \ 93 | f"RSI: {round(data['RSI'][-1], 2)}\n" 94 | self.update_quoter_stream(info) 95 | 96 | if action == 'buy' or action == 'sell': 97 | if action_res['result'] == 'success': 98 | # update the strategy stream after action 99 | self.update_strategy_stream(action_res) 100 | # save the trading history 101 | self.save_strategy_actions(action_res) 102 | self.update_strategy_profile() 103 | # Send the trading action email and notification to the GUI 104 | self.send_notification_via_email(action_res) 105 | -------------------------------------------------------------------------------- /strategy/Strategy.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pandas as pd 3 | import numpy as np 4 | import pandas_ta as pta 5 | import schedule 6 | 7 | from discord_bot.discord_notify_bot import run_bot_send_msg_new_thread 8 | from quoter.quoter_Webull import Quoter_Webull 9 | from quoter.quoter_Yahoo import Quoter_Yahoo 10 | from utils.dataIO import write_trading_log_json, get_current_time, logging_info 11 | from utils.send_email import send_emails 12 | 13 | 14 | class Strategy: 15 | """ 16 | Class Name must be the same as the file name 17 | """ 18 | 19 | def __init__(self, quoter="yahoo"): 20 | # define your own strategy name, and indicator 21 | self.strategy_name = "strategy template class" 22 | self.strategy_actions_history = "strategy_actions_history" 23 | self.parent = None # parent is the main program, app.py 24 | 25 | # set market data quoter 26 | # default is yh_quoter, fast and stable 27 | self.quoter = None 28 | if quoter == "yahoo": 29 | self.quoter = Quoter_Yahoo() 30 | elif quoter == "webull": 31 | self.quoter = Quoter_Webull() 32 | 33 | self.cash_balance = 0 34 | self.market_value = 0 35 | self.dayBuyingPower = 0 36 | self.overnightBuyingPower = 0 37 | self.net_account_value = 0 38 | 39 | # schedule the strategy to run 40 | # self.strategy_scheduler() 41 | self.strategy_load_notification() 42 | 43 | """ 44 | Response time: 45 | wb_quoter: around 300 - 800 ms 46 | yh_quoter: around 50 - 400 ms (fairly stable) 47 | 48 | Intraday (exclude pre-market and after-market): 49 | < 15 min: yh_quoter will get more data 50 | > 15 min: Quoter_Webull will get more data 51 | = 1h: yh_quoter will get more data 52 | >= 1d: yh_quoter will get more data 53 | 54 | Intraday (include pre-market and after-market): 55 | < 15 min: yh_quoter will get more data 56 | > 15 min: yh_quoter will get more data 57 | = 1h: yh_quoter will get more data 58 | >= 1d: yh_quoter will get more data 59 | 60 | """ 61 | 62 | # ****** 63 | # Functions below need to overwrite in your own strategy class 64 | def strategy_decision(self): 65 | pass 66 | 67 | # ****** 68 | # Functions below do not need to be modified, free to check and call 69 | def strategy_load_notification(self): 70 | logging_info(f"Strategy: {self.strategy_name} Status: Initialized") 71 | 72 | def save_strategy_actions(self, action_res): 73 | if action_res: 74 | write_trading_log_json("trader/trading_actions.json", action_res) 75 | 76 | def update_strategy_profile(self): 77 | # after placing order, update the strategy profile 78 | # you can define attributes depends on your strategy 79 | self.parent.trader.update_account_info() 80 | self.cash_balance = self.parent.trader.cash_balance 81 | self.market_value = self.parent.trader.market_value 82 | self.dayBuyingPower = self.parent.trader.dayBuyingPower 83 | self.overnightBuyingPower = self.parent.trader.overnightBuyingPower 84 | self.net_account_value = self.parent.trader.net_account_value 85 | 86 | def send_notification_via_email(self, action_res): 87 | # send_emails(from_, to, bcc: list, msg_subject, msg_body, login_email, login_password): 88 | # set the email and password in the app GUI 89 | 90 | # 1. send via email 91 | if self.parent.enable_email_notify and self.parent.is_email_setup(): 92 | from_ = self.parent.sender_email 93 | to = self.parent.receiver_email_1 94 | bcc = self.parent.receiver_email_2_bcc.split(" ") 95 | msg_subject = f"Strategy: {self.strategy_name}, Action made for {action_res['stock_info']}" 96 | 97 | msg_body = "" 98 | for key, value in action_res.items(): 99 | msg_body += f"{key}: {value} \n" 100 | 101 | login_email = self.parent.sender_email 102 | login_password = self.parent.sender_password 103 | send_emails( 104 | from_, to, bcc, msg_subject, msg_body, login_email, login_password 105 | ) 106 | 107 | # save the email to txt file in local as well 108 | msg_to_txt = ( 109 | f"{get_current_time()}\n" 110 | f"From: {from_}\n" 111 | f"To: {to}\n" 112 | f"Subject: {msg_subject}\n\n" 113 | f"{msg_body}\n\n" 114 | ) 115 | self.write_message_to_txt(msg_to_txt) 116 | logging_info(f"Strategy: {self.strategy_name} Status: Email sent") 117 | 118 | def send_notification_via_discord(self, action_res): 119 | # 2. send via discord bot 120 | # TODO: add discord bot notification 121 | msg_body = "" 122 | for key, value in action_res.items(): 123 | msg_body += f"{key}: {value} \n" 124 | run_bot_send_msg_new_thread(msg_body) 125 | 126 | msg_to_txt = ( 127 | f"{get_current_time()}\n" 128 | f"From: LukeLab\n" 129 | f"To: Discord\n" 130 | f"Subject: {self.strategy_name}\n\n" 131 | f"{msg_body}\n\n" 132 | ) 133 | self.write_message_to_txt(msg_to_txt) 134 | logging_info(f"Strategy: {self.strategy_name} Status: Discord msg sent") 135 | 136 | def write_message_to_txt(self, msg): 137 | with open("message_list.txt", "a") as file: 138 | file.write(msg) 139 | self.parent.frames["Message"].update_message_list() 140 | 141 | def update_quoter_stream(self, info): 142 | # update the quoter stream in the GUI 143 | self.parent.frames["StrategyMonitor"].set_quoter_stream(info) 144 | 145 | def update_strategy_stream(self, info): 146 | # update the strategy stream in the GUI 147 | if info != "": 148 | dict_str = "\n".join([f"{key}: {value}" for key, value in info.items()]) 149 | self.parent.frames["StrategyMonitor"].set_strategy_stream(dict_str) 150 | 151 | def get_current_position(self): 152 | return self.parent.trader._webull.get_account()["positions"] 153 | 154 | def check_1m_bar(self, stock): 155 | if self.quoter: 156 | return self.quoter.get_1min_bar( 157 | stock=stock, count="max", extend_trading=False 158 | ) 159 | 160 | def check_2m_bar(self, stock): 161 | if self.quoter: 162 | return self.quoter.get_2min_bar( 163 | stock=stock, count="max", extend_trading=False 164 | ) 165 | 166 | def check_5m_bar(self, stock): 167 | if self.quoter: 168 | return self.quoter.get_5min_bar( 169 | stock=stock, count="max", extend_trading=False 170 | ) 171 | 172 | def check_15m_bar(self, stock): 173 | if self.quoter: 174 | return self.quoter.get_15min_bar( 175 | stock=stock, count="max", extend_trading=False 176 | ) 177 | 178 | def check_30m_bar(self, stock): 179 | if self.quoter: 180 | return self.quoter.get_30min_bar( 181 | stock=stock, count="max", extend_trading=False 182 | ) 183 | 184 | def check_1h_bar(self, stock): 185 | if self.quoter: 186 | return self.quoter.get_1h_bar(stock=stock, count="1y", extend_trading=False) 187 | 188 | def check_1d_bar(self, stock): 189 | if self.quoter: 190 | return self.quoter.get_1d_bar(stock=stock, count="1y", extend_trading=False) 191 | 192 | def check_1w_bar(self, stock): 193 | if self.quoter: 194 | return self.quoter.get_1w_bar(stock=stock, count="1y", extend_trading=False) 195 | -------------------------------------------------------------------------------- /strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/strategy/__init__.py -------------------------------------------------------------------------------- /trader/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukeWang01/Program-Trading-Based-on-Webull/8080a0bc4e604d721ca6b9521b4c8b3bb67d63ce/trader/__init__.py -------------------------------------------------------------------------------- /utils/SQLiteHelper.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | 4 | class SQLiteHelper: 5 | def __init__(self, db_name): 6 | self.db_name = db_name 7 | self.conn = None 8 | self.cursor = None 9 | self.init_trader_info() 10 | self.init_email_notification_table() 11 | self.init_trader_profile_table() 12 | # self.init_ 13 | 14 | def connect(self): 15 | self.conn = sqlite3.connect(self.db_name) 16 | self.cursor = self.conn.cursor() 17 | 18 | def create_table(self, table_name, columns): 19 | query = f"CREATE TABLE IF NOT EXISTS {table_name} ({columns})" 20 | self.cursor.execute(query) 21 | self.conn.commit() 22 | 23 | def get_data(self, table_name, columns="*", condition=None): 24 | query = f"SELECT {columns} FROM {table_name}" 25 | if condition: 26 | query += f" WHERE {condition}" 27 | self.cursor.execute(query) 28 | return self.cursor.fetchall() 29 | 30 | def update_data(self, table_name, column_value_pairs, condition): 31 | query = f"UPDATE {table_name} SET " 32 | query += ", ".join([f"{column} = ?" for column in column_value_pairs]) 33 | query += f" WHERE {condition}" 34 | values = list(column_value_pairs.values()) 35 | self.cursor.execute(query, values) 36 | self.conn.commit() 37 | 38 | def insert_data(self, table_name, data): 39 | query = f"INSERT INTO {table_name} ({', '.join(data.keys())}) VALUES ({', '.join(['?' for _ in data.values()])})" 40 | self.cursor.execute(query, tuple(data.values())) 41 | self.conn.commit() 42 | 43 | def table_exists(self, table_name): 44 | query = "SELECT name FROM sqlite_master WHERE type='table' AND name=?" 45 | cursor = self.conn.execute(query, (table_name,)) 46 | return cursor.fetchone() is not None 47 | 48 | def close_connection(self): 49 | if self.cursor: 50 | self.cursor.close() 51 | if self.conn: 52 | self.conn.close() 53 | self.cursor = None 54 | self.conn = None 55 | 56 | def reconnect(self): 57 | self.close_connection() 58 | self.connect() 59 | 60 | # Utility functions: 61 | def init_trader_info(self): 62 | self.connect() 63 | table_name = "trader_info" 64 | columns = "email TEXT, access_token TEXT, device_name TEXT, did TEXT, uuid TEXT" 65 | if not self.table_exists(table_name): 66 | self.create_table(table_name, columns) 67 | self.insert_data(table_name, {"email": "", "access_token": "", "device_name": "", "did": "", "uuid": ""}) 68 | self.close_connection() 69 | 70 | def init_email_notification_table(self): 71 | self.connect() 72 | table_name = "email_notification" 73 | columns = "sender_email TEXT, sender_password TEXT, " \ 74 | "receiver_email_1 TEXT, receiver_email_2_bcc TEXT, enable_email_notify INTEGER" 75 | if not self.table_exists(table_name): 76 | self.create_table(table_name, columns) 77 | self.insert_data(table_name, {"sender_email": "", "sender_password": "", 78 | "receiver_email_1": "", "receiver_email_2_bcc": "", 79 | "enable_email_notify": ""}) 80 | self.close_connection() 81 | 82 | def get_sender_email(self): 83 | self.connect() 84 | data = self.get_data("email_notification", "sender_email") 85 | self.close_connection() 86 | return data[0][0] 87 | 88 | def update_sender_email(self, sender_email): 89 | self.connect() 90 | self.update_data("email_notification", {"sender_email": sender_email}, "ROWID = 1") 91 | self.close_connection() 92 | 93 | def get_sender_password(self): 94 | self.connect() 95 | data = self.get_data("email_notification", "sender_password") 96 | self.close_connection() 97 | return data[0][0] 98 | 99 | def update_sender_password(self, sender_password): 100 | self.connect() 101 | self.update_data("email_notification", {"sender_password": sender_password}, "ROWID = 1") 102 | self.close_connection() 103 | 104 | def get_receiver_email_1(self): 105 | self.connect() 106 | data = self.get_data("email_notification", "receiver_email_1") 107 | self.close_connection() 108 | return data[0][0] 109 | 110 | def update_receiver_email_1(self, receiver_email_1): 111 | self.connect() 112 | self.update_data("email_notification", {"receiver_email_1": receiver_email_1}, "ROWID = 1") 113 | self.close_connection() 114 | 115 | def get_receiver_email_2_bcc(self): 116 | self.connect() 117 | data = self.get_data("email_notification", "receiver_email_2_bcc") 118 | self.close_connection() 119 | return data[0][0] 120 | 121 | def update_receiver_email_2_bcc(self, receiver_email_2_bcc): 122 | self.connect() 123 | self.update_data("email_notification", {"receiver_email_2_bcc": receiver_email_2_bcc}, "ROWID = 1") 124 | self.close_connection() 125 | 126 | def get_enable_email_notify(self): 127 | self.connect() 128 | data = self.get_data("email_notification", "enable_email_notify") 129 | self.close_connection() 130 | return data[0][0] 131 | 132 | def update_enable_email_notify(self, enable_email_notify): 133 | self.connect() 134 | self.update_data("email_notification", {"enable_email_notify": enable_email_notify}, "ROWID = 1") 135 | self.close_connection() 136 | 137 | def get_device_name(self): 138 | self.connect() 139 | data = self.get_data("trader_info", "device_name") 140 | self.close_connection() 141 | return data[0][0] 142 | 143 | def get_access_token(self): 144 | self.connect() 145 | data = self.get_data("trader_info", "access_token") 146 | self.close_connection() 147 | return data[0][0] 148 | 149 | def get_did(self): 150 | self.connect() 151 | data = self.get_data("trader_info", "did") 152 | self.close_connection() 153 | return data[0][0] 154 | 155 | def get_uuid(self): 156 | self.connect() 157 | data = self.get_data("trader_info", "uuid") 158 | self.close_connection() 159 | return data[0][0] 160 | 161 | def get_email(self): 162 | self.connect() 163 | data = self.get_data("trader_info", "email") 164 | self.close_connection() 165 | return data[0][0] 166 | 167 | def update_did(self, did): 168 | self.connect() 169 | self.update_data("trader_info", {"did": did}, "ROWID = 1") 170 | self.close_connection() 171 | 172 | def update_uuid(self, uuid): 173 | self.connect() 174 | self.update_data("trader_info", {"uuid": uuid}, "ROWID = 1") 175 | self.close_connection() 176 | 177 | def update_access_token(self, access_token): 178 | self.connect() 179 | self.update_data("trader_info", {"access_token": access_token}, "ROWID = 1") 180 | self.close_connection() 181 | 182 | def update_device_name(self, device_name): 183 | self.connect() 184 | self.update_data("trader_info", {"device_name": device_name}, "ROWID = 1") 185 | self.close_connection() 186 | 187 | def update_email(self, email): 188 | self.connect() 189 | self.update_data("trader_info", {"email": email}, "ROWID = 1") 190 | self.close_connection() 191 | 192 | def init_trader_profile_table(self): 193 | self.connect() 194 | table_name = "trader_profile_table" 195 | columns = "PID_expired INTEGER, save_user_email INTEGER" 196 | if not self.table_exists(table_name): 197 | self.create_table(table_name, columns) 198 | self.insert_data(table_name, {"PID_expired": 15, "save_user_email": 1}) 199 | 200 | def get_PID_expired(self): 201 | self.connect() 202 | data = self.get_data("trader_profile_table", "PID_expired") 203 | self.close_connection() 204 | return data[0][0] 205 | 206 | def update_PID_expired(self, pid_expired): 207 | self.connect() 208 | self.update_data("trader_profile_table", {"PID_expired": pid_expired}, "ROWID = 1") 209 | self.close_connection() 210 | 211 | def get_save_user_email(self): 212 | self.connect() 213 | data = self.get_data("trader_profile_table", "save_user_email") 214 | self.close_connection() 215 | return data[0][0] 216 | 217 | def update_save_user_email(self, save_user_email): 218 | self.connect() 219 | self.update_data("trader_profile_table", {"save_user_email": save_user_email}, "ROWID = 1") 220 | self.close_connection() 221 | 222 | -------------------------------------------------------------------------------- /utils/dataIO.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import pandas 3 | import logging 4 | import os 5 | import json 6 | 7 | """ 8 | Data I/O: 9 | """ 10 | 11 | 12 | # Set up pandas data output: 13 | def save_to_csv(directory: str, filename, stock_data_frame: pandas.DataFrame): 14 | # directory = f"../data/{stock}" 15 | if not os.path.exists(directory): 16 | os.makedirs(directory) 17 | stock_data_frame.to_csv(os.path.join(directory, f"{filename}.csv"), index=True) 18 | 19 | 20 | def save_to_xls(directory: str, filename, stock_data_frame: pandas.DataFrame): 21 | # directory = f"../data/{stock}" 22 | if not os.path.exists(directory): 23 | os.makedirs(directory) 24 | stock_data_frame.to_excel(os.path.join(directory, f"{filename}.csv"), index=True) 25 | 26 | 27 | def save_to_json(directory: str, filename, stock_data_frame: pandas.DataFrame): 28 | # directory = f"../data/{stock}" 29 | if not os.path.exists(directory): 30 | os.makedirs(directory) 31 | stock_data_frame.to_json(os.path.join(directory, f"{filename}.json"), index=True) 32 | 33 | 34 | # Set up app running log 35 | def set_up_app_logging(): 36 | # set up logging 37 | log_file = os.path.join(os.getcwd(), 'app_running.log') 38 | logging.basicConfig(filename=log_file, level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') 39 | 40 | 41 | def logging_info(message: str): 42 | set_up_app_logging() 43 | logging.info(f'>>> {message}') # write log 44 | 45 | 46 | def logging_warning(message: str): 47 | set_up_app_logging() 48 | logging.warning(f'>>> {message}') # write log 49 | 50 | 51 | def logging_error(message: str): 52 | set_up_app_logging() 53 | logging.error(f'>>> {message}') # write log 54 | 55 | 56 | def logging_critical(message: str): 57 | set_up_app_logging() 58 | logging.critical(f'>>> {message}') # write log 59 | 60 | 61 | # Write trading history/log json file 62 | def write_trading_log_json(filename: str, trading_data: dict): 63 | try: 64 | with open(filename, 'r') as json_file: 65 | existing_data = json.load(json_file) 66 | except (json.JSONDecodeError, FileNotFoundError): 67 | existing_data = [] 68 | 69 | # Append new data to the existing list 70 | existing_data.append(trading_data) 71 | 72 | # Write the updated list back to the file 73 | with open(filename, 'w') as json_file: 74 | json.dump(existing_data, json_file, indent=4) 75 | 76 | 77 | def read_log_msg(): 78 | # Specify the path to the log file 79 | log_file_path = 'app_running.log' 80 | msg = '' 81 | # Open the log file for reading 82 | with open(log_file_path, 'r') as log_file: 83 | # Read the entire content of the file 84 | for line in log_file: 85 | if 'INFO' in line or 'ERROR' in line or 'CRITICAL' in line: 86 | msg += line 87 | 88 | # Print or process the content 89 | return msg 90 | 91 | 92 | def read_log_DEBUG(): 93 | # Specify the path to the log file 94 | log_file_path = 'app_running.log' 95 | debug = '' 96 | # Open the log file for reading 97 | with open(log_file_path, 'r') as log_file: 98 | # Read the entire content of the file 99 | for line in log_file: 100 | if 'DEBUG' in line: 101 | debug += line 102 | 103 | # Print or process the content 104 | return debug 105 | 106 | 107 | def get_current_time(): 108 | current_time = datetime.datetime.now() 109 | formatted_time = current_time.strftime('%Y-%m-%d %H:%M:%S') + ' | ' + f'{current_time.microsecond // 1000:03d} ms' 110 | return formatted_time 111 | 112 | 113 | def is_market_hours(): 114 | current_time = datetime.datetime.now().time() 115 | market_open_time = datetime.time(9, 30) # Regular market open time (9:30 AM) 116 | market_close_time = datetime.time(16, 0) # Regular market close time (4:00 PM) 117 | 118 | if market_open_time <= current_time <= market_close_time: 119 | return True 120 | else: 121 | return False 122 | 123 | 124 | def read_json_file(file_path): 125 | if os.path.exists(file_path): 126 | with open(file_path, 'r') as file: 127 | data = json.load(file) 128 | return data 129 | else: 130 | return [] 131 | 132 | 133 | def write_json_file(file_path, data): 134 | with open(file_path, 'w') as file: 135 | json.dump(data, file, indent=2) 136 | 137 | -------------------------------------------------------------------------------- /utils/download_max_history_candles.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from quoter.quoter_Webull import Quoter_Webull 4 | from quoter.quoter_Yahoo import Quoter_Yahoo 5 | from utils.dataIO import save_to_csv, save_to_xls, save_to_json, logging_error 6 | 7 | """ 8 | Download stock history data: 9 | 10 | 1. Define stock list below, type the tickers you want to download 11 | 2. Run this script 12 | 3. Data will be saved in ../data/ticker_name/ 13 | 14 | OR, you can modify the functions to save whatever you want. 15 | 16 | Note: 17 | 1min, 2min, 1h, 1d data are from Yahoo, 5min, 15min, 30min data are from Webull. 18 | Download max history data by default, it may take about 2 minutes, please be patient. 19 | 20 | """ 21 | 22 | 23 | def download_max_history_candles(stock, count='max', save_format='csv'): 24 | """ 25 | :param stock: ticker 26 | :param count: max 1200 for 1d 27 | :param save_format: 'csv' or 'xls' 28 | :return: pandas dataframe 29 | """ 30 | 31 | yh_quoter = Quoter_Yahoo() 32 | wb_quoter = Quoter_Webull() 33 | 34 | stock = stock.upper() 35 | response_1h_bar = yh_quoter.get_1h_bar(stock=stock, count=count, extend_trading=True) 36 | response_1d_bar = yh_quoter.get_1d_bar(stock=stock, count=count, extend_trading=False) 37 | 38 | save_dir = f'data/{stock}/' 39 | current_date = datetime.datetime.today().strftime('%Y%m%d') 40 | 41 | if response_1h_bar.empty and response_1d_bar.empty: 42 | logging_error('Invalid ticker or interval') 43 | else: 44 | if save_format == 'csv': 45 | save_to_csv(save_dir, f'{stock}_{current_date}_1h', response_1h_bar) 46 | save_to_csv(save_dir, f'{stock}_{current_date}_1d', response_1d_bar) 47 | elif save_format == 'xls': 48 | save_to_xls(save_dir, f'{stock}_{current_date}_1h', response_1h_bar) 49 | save_to_xls(save_dir, f'{stock}_{current_date}_1h', response_1d_bar) 50 | elif save_format == 'json': 51 | save_to_json(save_dir, f'{stock}_{current_date}_1h', response_1h_bar) 52 | save_to_json(save_dir, f'{stock}_{current_date}_1h', response_1d_bar) 53 | -------------------------------------------------------------------------------- /utils/format_str.py: -------------------------------------------------------------------------------- 1 | import locale 2 | 3 | 4 | def format_financial_number(input_number): 5 | number = float(input_number) 6 | # Save the current locale setting 7 | current_locale = locale.getlocale() 8 | 9 | # Set the locale to the desired financial format (e.g., en_US for US format) 10 | locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') 11 | 12 | # Format the number as a financial string 13 | formatted_number = locale.currency(number, grouping=True) 14 | 15 | # Reset the locale to the original setting 16 | locale.setlocale(locale.LC_ALL, current_locale) 17 | 18 | return formatted_number 19 | -------------------------------------------------------------------------------- /utils/input_check.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def is_valid_email(email): 5 | # TODO: check email if is a list of emails 6 | pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$' 7 | return re.match(pattern, email) is not None 8 | 9 | 10 | def is_valid_phone_number(phone_number): 11 | # phone must be in format +[country_code]-[your number] 12 | pattern = r'^\+\d+-\d+$' 13 | return re.match(pattern, phone_number) is not None 14 | 15 | 16 | def is_valid_pid(pid): 17 | return pid.isdigit() and len(pid) == 6 18 | -------------------------------------------------------------------------------- /utils/play_sound.py: -------------------------------------------------------------------------------- 1 | import simpleaudio as sa 2 | 3 | # Note: there will be no sound when the code exit immediately 4 | 5 | 6 | def order_placed(): 7 | # Load the sound file 8 | wave_obj = sa.WaveObject.from_wave_file('audio/order_placed.wav') 9 | 10 | # Play the sound file 11 | play_obj = wave_obj.play() 12 | 13 | # Wait for the sound to finish playing 14 | # play_obj.wait_done() 15 | 16 | 17 | def strategy_notified(): 18 | # Load the sound file 19 | wave_obj = sa.WaveObject.from_wave_file('audio/strategy_notified.wav') 20 | 21 | # Play the sound file 22 | play_obj = wave_obj.play() 23 | 24 | # Wait for the sound to finish playing 25 | # play_obj.wait_done() 26 | -------------------------------------------------------------------------------- /utils/send_email.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.text import MIMEText 3 | from email.mime.multipart import MIMEMultipart 4 | 5 | 6 | def send_email(from_, to, msg_subject, msg_body, login_email, login_password): 7 | # create message 8 | msg = MIMEMultipart() 9 | msg['From'] = from_ 10 | msg['To'] = to 11 | msg['Subject'] = msg_subject 12 | 13 | # add text to message 14 | msg.attach(MIMEText(msg_body)) 15 | 16 | # setup gmail 17 | smtp_server = 'smtp.gmail.com' 18 | smtp_port = 587 19 | smtp_username = login_email 20 | smtp_password = login_password 21 | 22 | with smtplib.SMTP(smtp_server, smtp_port) as server: 23 | server.starttls() 24 | server.login(smtp_username, smtp_password) 25 | server.send_message(msg) 26 | 27 | 28 | def send_emails(from_, to, bcc: list, msg_subject, msg_body, login_email, login_password): 29 | # send an email to multiple recipients 30 | bcc_emails = bcc # BCC 31 | message = msg_body 32 | 33 | msg = MIMEText(message) 34 | msg['Subject'] = msg_subject 35 | msg['From'] = from_ 36 | msg['To'] = to 37 | # msg["Cc"] = '' 38 | # msg['Bcc'] = ', '.join(receiver_emails) 39 | # toaddrs = [msg['To']] + [msg['Bcc']] 40 | 41 | with smtplib.SMTP('smtp.gmail.com', 587) as server: 42 | server.starttls() 43 | server.login(login_email, login_password) 44 | server.sendmail(from_, [to] + bcc_emails, msg.as_string()) 45 | 46 | -------------------------------------------------------------------------------- /utils/send_text_message.py: -------------------------------------------------------------------------------- 1 | from twilio.rest import Client 2 | 3 | 4 | # For future use only 5 | def send_txt_message(from_, to, msg_body, twilio_account_sid, twilio_auth_token): 6 | # send text message via twilio 7 | 8 | # Your Account SID from twilio.com/console 9 | account_sid = twilio_account_sid 10 | # Your Auth Token from twilio.com/console 11 | auth_token = twilio_auth_token 12 | client = Client(account_sid, auth_token) 13 | message = client.messages.create( 14 | to=to, 15 | from_=from_, 16 | body=msg_body) 17 | print(message.sid) 18 | 19 | -------------------------------------------------------------------------------- /utils/update_intraday_data_history.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from quoter.quoter_Webull import Quoter_Webull 4 | from quoter.quoter_Yahoo import Quoter_Yahoo 5 | from utils.dataIO import save_to_csv 6 | 7 | """ 8 | Download stock history data: 9 | 10 | 1. Define stock list below, type the tickers you want to download 11 | 2. Run this script 12 | 3. Data will be saved in ../data/ticker_name/ 13 | 14 | OR, you can modify the functions to save whatever you want. 15 | 16 | Note: 17 | 1min, 2min, 1h, 1d data are from Yahoo, 5min, 15min, 30min data are from Webull. 18 | Download max history data by default, it may take about 2 minutes, please be patient. 19 | 20 | """ 21 | 22 | 23 | def update_intraday_data_history(stock): 24 | # run every weekend 25 | yh_quoter = Quoter_Yahoo() 26 | wb_quoter = Quoter_Webull() 27 | 28 | stock = stock.upper() 29 | current_date = datetime.datetime.today().strftime('%Y%m%d') 30 | 31 | save_dir = f'data/{stock}/intraday/' 32 | 33 | # get 1min bar: yahoo quoter has more data 34 | response_1min_bar = yh_quoter.get_1min_bar(stock=stock, count='max', extend_trading=True) 35 | save_to_csv(save_dir, f'{stock}_{current_date}_1min', response_1min_bar) 36 | print(f'{stock}_{current_date}_1min, done') 37 | 38 | # get 2min bar: yahoo quoter has more data 39 | response_2min_bar = yh_quoter.get_2min_bar(stock=stock, count='max', extend_trading=True) 40 | save_to_csv(save_dir, f'{stock}_{current_date}_2min', response_2min_bar) 41 | print(f'{stock}_{current_date}_2min, done') 42 | 43 | # get 5min bar: webull quoter has more data 44 | response_5min_bar = wb_quoter.get_5min_bar(stock=stock, count='max', extend_trading=True) 45 | response_5min_bar = response_5min_bar.rename(columns={'open': 'Open', 'high': 'High', 'low': 'Low', 46 | 'close': 'Close', 'volume': 'Volume', 'vwap': 'VWAP'}) 47 | save_to_csv(save_dir, f'{stock}_{current_date}_5min', response_5min_bar) 48 | print(f'{stock}_{current_date}_5min, done') 49 | 50 | # get 15min bar: webull quoter has more data 51 | response_15min_bar = wb_quoter.get_15min_bar(stock=stock, count='max', extend_trading=True) 52 | response_15min_bar = response_15min_bar.rename(columns={'open': 'Open', 'high': 'High', 'low': 'Low', 53 | 'close': 'Close', 'volume': 'Volume', 'vwap': 'VWAP'}) 54 | save_to_csv(save_dir, f'{stock}_{current_date}_15min', response_15min_bar) 55 | print(f'{stock}_{current_date}_15min, done') 56 | 57 | # get 30min bar: webull quoter has more data 58 | response_30min_bar = wb_quoter.get_30min_bar(stock=stock, count='max', extend_trading=True) 59 | response_30min_bar = response_30min_bar.rename(columns={'open': 'Open', 'high': 'High', 'low': 'Low', 60 | 'close': 'Close', 'volume': 'Volume', 'vwap': 'VWAP'}) 61 | save_to_csv(save_dir, f'{stock}_{current_date}_30min', response_30min_bar) 62 | print(f'{stock}_{current_date}_30min, done') 63 | 64 | # get 1h bar: yahoo quoter has more data 65 | response_1h_bar = yh_quoter.get_1h_bar(stock=stock, count='max', extend_trading=True) 66 | save_to_csv(save_dir, f'{stock}_{current_date}_1h', response_1h_bar) 67 | print(f'{stock}_{current_date}_1h, done') 68 | 69 | 70 | 71 | --------------------------------------------------------------------------------