├── .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 |
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 |
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 |
137 |
138 |
139 |
140 |
141 |
142 | ## 🖥️ 4. App Features
143 |
144 | #### 4.1 Log in
145 |
146 |
147 | #### 4.2 Account Dashboard
148 |
149 |
150 | #### 4.3 Select and run strategy, monitor the market and strategy
151 |
152 |
153 | #### 4.4 Edit your profile settings, add notification email account
154 |
155 |
156 | #### 4.5 View the trading orders and transaction history
157 |
158 |
159 | #### 4.6 View the performance of your strategy or account
160 |
161 |
162 | #### 4.7 View the App running log
163 |
164 |
165 | #### 4.8 Edit the notification email account settings
166 |
167 |
168 | #### 4.9 Download the stock intraday and history data, saving as csv file
169 |
170 |
171 |
172 |
173 | #### 4.10 Safe exit the App
174 |
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 |
197 |
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 | 
9 |
10 | 
11 |
12 |
13 |
14 | ### 2. Get the uuid, did, access_token from the dev tools window:
15 |
16 |
17 |
18 | 
19 | 
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 | 
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 | 
30 |
31 | 
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 |
--------------------------------------------------------------------------------