Aluode commited on
Commit
7678b73
·
verified ·
1 Parent(s): 469224d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +491 -512
app.py CHANGED
@@ -1,512 +1,491 @@
1
- import streamlit as st
2
- import sqlite3
3
- import hashlib
4
- import threading
5
- import pygame
6
- from yfinance import Ticker
7
- import pandas as pd
8
- import plotly.express as px
9
- import requests
10
- from newsapi import NewsApiClient
11
- import os
12
- from pathlib import Path
13
- import base64
14
- from datetime import datetime
15
- import time
16
-
17
- # Constants
18
- INITIAL_BALANCE = 10000
19
- UPDATE_INTERVAL = 60
20
- DEFAULT_CURRENCY = 'USD'
21
- STOCK_LIST = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA", "META", "NVDA", "BRK-B", "V", "JPM"]
22
- TIME_RANGES = {
23
- '1 Day': '1d',
24
- '5 Days': '5d',
25
- '1 Month': '1mo',
26
- '3 Months': '3mo',
27
- '6 Months': '6mo',
28
- '1 Year': '1y',
29
- '5 Years': '5y',
30
- '10 Years': '10y',
31
- 'Max': 'max'
32
- }
33
- CURRENCIES = ["USD", "EUR", "GBP", "JPY", "AUD"]
34
-
35
- # Initialize NewsAPI Client
36
- NEWS_API_KEY = 'your_newsapi_key_here'
37
- newsapi = NewsApiClient(api_key=NEWS_API_KEY)
38
-
39
- # Initialize pygame for music
40
- def initialize_music():
41
- if 'music_initialized' not in st.session_state:
42
- try:
43
- pygame.mixer.init()
44
- pygame.mixer.music.load('trade.mp3')
45
- pygame.mixer.music.play(-1)
46
- st.session_state['music_initialized'] = True
47
- except pygame.error as e:
48
- st.error(f"Failed to load or play the music: {str(e)}. Please check that 'trade.mp3' exists in the correct directory.")
49
-
50
- # Database setup
51
- def setup_database():
52
- conn = sqlite3.connect('trading_game.db', check_same_thread=False)
53
- cursor = conn.cursor()
54
-
55
- # Create tables if they don't exist
56
- cursor.execute('''
57
- CREATE TABLE IF NOT EXISTS users (
58
- username TEXT PRIMARY KEY,
59
- password TEXT,
60
- balance REAL,
61
- initial_balance REAL,
62
- currency TEXT DEFAULT 'USD'
63
- )
64
- ''')
65
-
66
- cursor.execute('''
67
- CREATE TABLE IF NOT EXISTS portfolios (
68
- username TEXT,
69
- ticker TEXT,
70
- shares REAL,
71
- initial_investment REAL,
72
- PRIMARY KEY (username, ticker),
73
- FOREIGN KEY (username) REFERENCES users (username)
74
- )
75
- ''')
76
-
77
- cursor.execute('''
78
- CREATE TABLE IF NOT EXISTS financial_logs (
79
- username TEXT,
80
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
81
- total_value REAL,
82
- FOREIGN KEY (username) REFERENCES users (username)
83
- )
84
- ''')
85
-
86
- cursor.execute('''
87
- CREATE TABLE IF NOT EXISTS transactions (
88
- username TEXT,
89
- action TEXT,
90
- ticker TEXT,
91
- amount REAL,
92
- price REAL,
93
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
94
- )
95
- ''')
96
-
97
- conn.commit()
98
- return conn
99
-
100
- # Ensure Database Schema
101
- def ensure_database_schema():
102
- conn = sqlite3.connect('trading_game.db')
103
- c = conn.cursor()
104
-
105
- # Ensure the users table exists and has the required columns
106
- c.execute('''
107
- CREATE TABLE IF NOT EXISTS users (
108
- username TEXT PRIMARY KEY,
109
- password TEXT,
110
- balance REAL,
111
- initial_balance REAL,
112
- currency TEXT DEFAULT 'USD'
113
- )
114
- ''')
115
-
116
- # Check if the initial_balance column exists, if not, add it
117
- c.execute("PRAGMA table_info(users)")
118
- columns = [column[1] for column in c.fetchall()]
119
- if 'initial_balance' not in columns:
120
- c.execute(f"ALTER TABLE users ADD COLUMN initial_balance REAL DEFAULT {INITIAL_BALANCE}")
121
- conn.commit()
122
-
123
- # Ensure the portfolios table exists and has the required columns
124
- c.execute('''
125
- CREATE TABLE IF NOT EXISTS portfolios (
126
- username TEXT,
127
- ticker TEXT,
128
- shares REAL,
129
- initial_investment REAL,
130
- PRIMARY KEY (username, ticker),
131
- FOREIGN KEY (username) REFERENCES users (username)
132
- )
133
- ''')
134
-
135
- # Check if the initial_investment column exists, if not, add it
136
- c.execute("PRAGMA table_info(portfolios)")
137
- columns = [column[1] for column in c.fetchall()]
138
- if 'initial_investment' not in columns:
139
- c.execute(f"ALTER TABLE portfolios ADD COLUMN initial_investment REAL")
140
- conn.commit()
141
-
142
- conn.close()
143
-
144
- # Function to hash passwords
145
- def hash_password(password):
146
- return hashlib.sha256(password.encode()).hexdigest()
147
-
148
- # Function to set the background image
149
- def set_background_image(image_path):
150
- if Path(image_path).exists():
151
- try:
152
- with open(image_path, "rb") as img_file:
153
- encoded_img = base64.b64encode(img_file.read()).decode('utf-8')
154
- st.markdown(
155
- f"""
156
- <style>
157
- .stApp {{
158
- position: relative;
159
- background-image: url(data:image/png;base64,{encoded_img});
160
- background-size: cover;
161
- background-position: center;
162
- background-repeat: no-repeat;
163
- }}
164
- .stApp::before {{
165
- content: '';
166
- position: absolute;
167
- top: 0;
168
- left: 0;
169
- right: 0;
170
- bottom: 0;
171
- background-color: rgba(255, 255, 255, 0.8); /* White overlay with 0.8 opacity */
172
- z-index: 0;
173
- }}
174
- .main-title, .sub-title, .metric-box, .blue-background {{
175
- position: relative;
176
- z-index: 1;
177
- }}
178
- </style>
179
- """,
180
- unsafe_allow_html=True
181
- )
182
- except Exception as e:
183
- st.error(f"Error loading image: {str(e)}")
184
- else:
185
- st.error('Image not found. Please check the file name and path.')
186
-
187
- # Function to fetch the current price of stocks or currencies
188
- def get_stock_data(ticker, period='1d'):
189
- try:
190
- stock = Ticker(ticker)
191
- df = stock.history(period=period)
192
- if not df.empty:
193
- df.reset_index(inplace=True)
194
- return df
195
- else:
196
- return None
197
- except Exception as e:
198
- print(f"Error fetching data: {str(e)}")
199
- return None
200
-
201
- # Function to convert currency
202
- def convert_currency(amount, from_currency, to_currency):
203
- if from_currency == to_currency:
204
- return amount
205
- try:
206
- response = requests.get(f'https://api.exchangerate-api.com/v4/latest/{from_currency}')
207
- rates = response.json()['rates']
208
- conversion_rate = rates.get(to_currency, 1)
209
- return amount * conversion_rate
210
- except Exception as e:
211
- print(f"Error converting currency: {str(e)}")
212
- return amount
213
-
214
- # Function to get market status
215
- def get_market_status():
216
- market_status = {}
217
- for ticker in STOCK_LIST:
218
- df = get_stock_data(ticker, period='1d')
219
- if df is not None:
220
- open_price = df['Open'].iloc[0]
221
- current_price = df['Close'].iloc[-1]
222
- change = (current_price - open_price) / open_price * 100
223
- market_status[ticker] = change
224
- return market_status
225
-
226
- # Function to fetch and plot historical data for holdings
227
- def plot_holdings(holdings, time_range):
228
- fig = px.line(title=f"Holdings - {time_range}")
229
- for ticker in holdings:
230
- df = get_stock_data(ticker, period=TIME_RANGES[time_range])
231
- if df is not None and not df.empty:
232
- initial_investment = holdings[ticker]['initial_investment']
233
- fig.add_scatter(x=df['Date'], y=df['Close'], mode='lines', name=f"{ticker} (Invested: ${initial_investment:.2f})")
234
- st.plotly_chart(fig, use_container_width=True)
235
-
236
- # Function to display the market overview ticker
237
- def market_overview_ticker(market_status):
238
- ticker_text = ''.join([
239
- f"<span style='color: {'green' if change > 0 else 'red'};'>{ticker}: {change:.2f}%</span> | "
240
- for ticker, change in market_status.items()
241
- ])
242
- st.markdown(f"""
243
- <style>
244
- .ticker {{
245
- overflow: hidden;
246
- white-space: nowrap;
247
- width: 100%;
248
- background-color: black;
249
- color: white;
250
- padding: 5px;
251
- font-size: 14px;
252
- }}
253
- .ticker span {{
254
- display: inline-block;
255
- animation: ticker 20s linear infinite;
256
- }}
257
- @keyframes ticker {{
258
- 0% {{ transform: translateX(100%); }}
259
- 100% {{ transform: translateX(-100%); }}
260
- }}
261
- </style>
262
- <div class="ticker">
263
- <span>{ticker_text}</span>
264
- </div>
265
- """, unsafe_allow_html=True)
266
-
267
- # Function to fetch currency values
268
- def get_currency_values():
269
- currencies = ['EUR', 'GBP', 'JPY', 'AUD']
270
- currency_data = {}
271
- for currency in currencies:
272
- rate = convert_currency(1, 'USD', currency)
273
- currency_data[currency] = rate
274
- return currency_data
275
-
276
- # Function to fetch gold price from yfinance
277
- def get_gold_price():
278
- gold_ticker = Ticker("GC=F")
279
- df = gold_ticker.history(period="1d")
280
- if not df.empty:
281
- return df['Close'].iloc[-1]
282
- else:
283
- st.error("Failed to fetch the gold price.")
284
- return None
285
-
286
- # Function to display currency and gold values
287
- def display_currency_and_gold():
288
- st.markdown('<div class="sub-title">Currency and Gold Values</div>', unsafe_allow_html=True)
289
- currency_data = get_currency_values()
290
- gold_price = get_gold_price()
291
- for currency, value in currency_data.items():
292
- st.markdown(f'<div class="metric-box">{currency}/USD: {value:.2f}</div>', unsafe_allow_html=True)
293
- if gold_price:
294
- st.markdown(f'<div class="metric-box">Gold/USD: ${gold_price:.2f}</div>', unsafe_allow_html=True)
295
-
296
- # Function to display the user's financial overview
297
- def display_financial_overview(username, conn):
298
- st.subheader("Your Financial Overview")
299
- total_value, invested_value, initial_balance = calculate_total_value(username, conn)
300
- st.markdown(f'<div class="metric-box">Total Value (Including Investments): ${total_value:.2f}</div>', unsafe_allow_html=True)
301
- st.markdown(f'<div class="metric-box">Invested Value: ${invested_value:.2f}</div>', unsafe_allow_html=True)
302
- st.markdown(f'<div class="metric-box">Initial Investment: ${initial_balance:.2f}</div>', unsafe_allow_html=True)
303
-
304
- # Reintroducing get_user_data with initial_investment
305
- def get_user_data(username, conn):
306
- c = conn.cursor()
307
- c.execute("SELECT balance, currency, initial_balance FROM users WHERE username = ?", (username,))
308
- balance, currency, initial_balance = c.fetchone()
309
-
310
- c.execute("SELECT ticker, shares, initial_investment FROM portfolios WHERE username = ?", (username,))
311
- portfolio_data = c.fetchall()
312
- portfolio = {ticker: {"shares": shares, "initial_investment": initial_investment} for ticker, shares, initial_investment in portfolio_data}
313
-
314
- return balance, currency, initial_balance, portfolio
315
-
316
- # Function to calculate total value with comparison to initial investment
317
- def calculate_total_value(username, conn):
318
- balance, currency, initial_balance, portfolio = get_user_data(username, conn)
319
- total_value = balance
320
- invested_value = 0
321
- for ticker, data in portfolio.items():
322
- if data['shares'] > 0:
323
- stock_data = get_stock_data(ticker, '1d')
324
- if stock_data is not None:
325
- last_close = stock_data['Close'].iloc[-1]
326
- invested_value += last_close * data['shares']
327
- total_value += invested_value
328
- total_value = convert_currency(total_value, 'USD', currency)
329
- invested_value = convert_currency(invested_value, 'USD', currency)
330
- initial_balance_converted = convert_currency(initial_balance, 'USD', currency)
331
- return total_value, invested_value, initial_balance_converted
332
-
333
- # Function to buy stock
334
- def buy_stock(username, ticker, amount, conn, currency):
335
- current_price = get_stock_data(ticker, period='1d')
336
-
337
- if current_price is None or current_price.empty:
338
- return "Invalid ticker symbol."
339
-
340
- total_cost = current_price['Close'].iloc[-1] * amount
341
-
342
- c = conn.cursor()
343
- c.execute("SELECT balance, currency FROM users WHERE username = ?", (username,))
344
- user_balance, user_currency = c.fetchone()
345
-
346
- total_cost_in_user_currency = convert_currency(total_cost, 'USD', user_currency)
347
-
348
- if user_balance < total_cost_in_user_currency:
349
- return "Insufficient funds."
350
-
351
- new_balance = user_balance - total_cost_in_user_currency
352
- c.execute("UPDATE users SET balance = ? WHERE username = ?", (new_balance, username))
353
-
354
- c.execute("SELECT shares FROM portfolios WHERE username = ? AND ticker = ?", (username, ticker))
355
- existing_shares = c.fetchone()
356
-
357
- if existing_shares:
358
- new_shares = existing_shares[0] + amount
359
- c.execute("UPDATE portfolios SET shares = ?, initial_investment = initial_investment + ? WHERE username = ? AND ticker = ?", (new_shares, total_cost, username, ticker))
360
- else:
361
- c.execute("INSERT INTO portfolios (username, ticker, shares, initial_investment) VALUES (?, ?, ?, ?)", (username, ticker, amount, total_cost))
362
-
363
- c.execute("INSERT INTO transactions (username, action, ticker, amount, price) VALUES (?, ?, ?, ?, ?)",
364
- (username, 'Buy', ticker, amount, current_price['Close'].iloc[-1]))
365
-
366
- conn.commit()
367
- return f"Successfully bought {amount} shares of {ticker} for ${total_cost_in_user_currency:.2f} {user_currency}"
368
-
369
- # Function to sell stock
370
- def sell_stock(username, ticker, amount, conn, currency):
371
- current_price = get_stock_data(ticker, period='1d')
372
-
373
- if current_price is None or current_price.empty:
374
- return "Invalid ticker symbol."
375
-
376
- total_value = current_price['Close'].iloc[-1] * amount
377
-
378
- c = conn.cursor()
379
- c.execute("SELECT shares FROM portfolios WHERE username = ? AND ticker = ?", (username, ticker))
380
- existing_shares = c.fetchone()
381
-
382
- if not existing_shares or existing_shares[0] < amount:
383
- return "Insufficient shares to sell."
384
-
385
- new_shares = existing_shares[0] - amount
386
- if new_shares == 0:
387
- c.execute("DELETE FROM portfolios WHERE username = ? AND ticker = ?", (username, ticker))
388
- else:
389
- c.execute("UPDATE portfolios SET shares = ? WHERE username = ? AND ticker = ?", (new_shares, username, ticker))
390
-
391
- c.execute("SELECT balance, currency FROM users WHERE username = ?", (username,))
392
- user_balance, user_currency = c.fetchone()
393
- total_value_in_user_currency = convert_currency(total_value, 'USD', user_currency)
394
- new_balance = user_balance + total_value_in_user_currency
395
- c.execute("UPDATE users SET balance = ? WHERE username = ?", (new_balance, username))
396
-
397
- c.execute("INSERT INTO transactions (username, action, ticker, amount, price) VALUES (?, ?, ?, ?, ?)",
398
- (username, 'Sell', ticker, amount, current_price['Close'].iloc[-1]))
399
-
400
- conn.commit()
401
- return f"Successfully sold {amount} shares of {ticker} for ${total_value_in_user_currency:.2f} {user_currency}"
402
-
403
- # Main function with enhanced layout and features
404
- def main():
405
- ensure_database_schema()
406
- initialize_music()
407
-
408
- if 'logged_in' not in st.session_state:
409
- st.session_state.logged_in = False
410
- if 'username' not in st.session_state:
411
- st.session_state.username = None
412
-
413
- st.markdown('<div class="main-title" style="background-color: blue; color: white; padding: 10px;">Charging Bull Trader</div>', unsafe_allow_html=True)
414
-
415
- if not st.session_state.logged_in:
416
- st.markdown('<div class="sub-title">Login</div>', unsafe_allow_html=True)
417
- username_input = st.text_input("Username")
418
- password_input = st.text_input("Password", type="password")
419
- if st.button("Login"):
420
- conn = setup_database()
421
- c = conn.cursor()
422
- c.execute("SELECT * FROM users WHERE username=? AND password=?", (username_input, hash_password(password_input)))
423
- if c.fetchone():
424
- st.session_state.logged_in = True
425
- st.session_state.username = username_input
426
- else:
427
- st.error("Invalid username or password")
428
-
429
- if st.button("Create Account"):
430
- conn = setup_database()
431
- c = conn.cursor()
432
- try:
433
- c.execute("INSERT INTO users (username, password, balance, initial_balance, currency) VALUES (?, ?, ?, ?, ?)",
434
- (username_input, hash_password(password_input), INITIAL_BALANCE, INITIAL_BALANCE, DEFAULT_CURRENCY))
435
- for ticker in STOCK_LIST:
436
- c.execute("INSERT INTO portfolios (username, ticker, shares, initial_investment) VALUES (?, ?, 0, 0)", (username_input, ticker))
437
- conn.commit()
438
- st.success("Account created successfully!")
439
- except sqlite3.IntegrityError:
440
- st.error("Username already exists.")
441
- else:
442
- username = st.session_state.username
443
- conn = setup_database()
444
- st.sidebar.write(f"Welcome, {username}!")
445
- if st.sidebar.button("Logout"):
446
- st.session_state.logged_in = False
447
- st.session_state.username = None
448
-
449
- # Add the small Charging Bull image under the Logout button
450
- st.sidebar.image("trade.png", width=150)
451
-
452
- display_financial_overview(username, conn)
453
-
454
- col1, col2 = st.columns(2)
455
-
456
- with col1:
457
- st.markdown('<div class="sub-title">Personal Holdings and Stocks</div>', unsafe_allow_html=True)
458
- time_range = st.selectbox("Select Time Range for Holdings", list(TIME_RANGES.keys()))
459
- _, _, _, portfolio = get_user_data(username, conn)
460
- if portfolio:
461
- plot_holdings(portfolio, time_range)
462
- else:
463
- st.write("No stocks in your portfolio.")
464
-
465
- st.markdown('<div class="sub-title">Buy/Sell Stocks</div>', unsafe_allow_html=True)
466
- action = st.selectbox("Choose Action", ["Buy", "Sell"])
467
- ticker = st.text_input("Ticker")
468
- amount = st.number_input("Amount", min_value=1, step=1)
469
- selected_currency = st.selectbox("Currency", CURRENCIES)
470
-
471
- if st.button(f"{action} Stocks"):
472
- if action == "Buy":
473
- result = buy_stock(username, ticker, amount, conn, selected_currency)
474
- else:
475
- result = sell_stock(username, ticker, amount, conn, selected_currency)
476
- st.write(result)
477
-
478
- with col2:
479
- market_status = get_market_status()
480
- market_overview_ticker(market_status)
481
- display_currency_and_gold()
482
-
483
- st.markdown('<div class="sub-title">Stock Lookup</div>', unsafe_allow_html=True)
484
- stock_ticker = st.text_input("Enter Stock Ticker for Lookup")
485
- time_range_lookup = st.selectbox("Select Time Range", list(TIME_RANGES.keys()), key="lookup")
486
- if st.button("Lookup"):
487
- df = get_stock_data(stock_ticker, period=TIME_RANGES[time_range_lookup])
488
- if df is not None and not df.empty:
489
- fig = px.line(df, x='Date', y='Close', title=f"{stock_ticker} - {time_range_lookup}")
490
- st.plotly_chart(fig, use_container_width=True)
491
- else:
492
- st.write("No data available for this ticker.")
493
-
494
- st.markdown('<div class="sub-title">Stock Market News</div>', unsafe_allow_html=True)
495
- news_ticker = st.text_input("Enter Stock Ticker for News")
496
- news_api_key = st.text_input("Enter NewsAPI Key")
497
- if st.button("Get News"):
498
- if news_api_key:
499
- newsapi = NewsApiClient(api_key=news_api_key)
500
- articles = newsapi.get_everything(q=news_ticker, language='en', sort_by='publishedAt', page_size=5)
501
- if articles:
502
- for article in articles['articles']:
503
- st.subheader(article['title'])
504
- st.write(article['description'])
505
- st.write(f"[Read more]({article['url']})")
506
- else:
507
- st.write("No news available for this ticker.")
508
- else:
509
- st.write("Please enter a valid NewsAPI key.")
510
-
511
- if __name__ == "__main__":
512
- main()
 
1
+ import streamlit as st
2
+ import sqlite3
3
+ import hashlib
4
+ import threading
5
+ import pygame
6
+ from yfinance import Ticker
7
+ import pandas as pd
8
+ import plotly.express as px
9
+ import requests
10
+ import os
11
+ from pathlib import Path
12
+ import base64
13
+ from datetime import datetime
14
+ import time
15
+
16
+ # Constants
17
+ INITIAL_BALANCE = 10000
18
+ UPDATE_INTERVAL = 60
19
+ DEFAULT_CURRENCY = 'USD'
20
+ STOCK_LIST = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA", "META", "NVDA", "BRK-B", "V", "JPM"]
21
+ TIME_RANGES = {
22
+ '1 Day': '1d',
23
+ '5 Days': '5d',
24
+ '1 Month': '1mo',
25
+ '3 Months': '3mo',
26
+ '6 Months': '6mo',
27
+ '1 Year': '1y',
28
+ '5 Years': '5y',
29
+ '10 Years': '10y',
30
+ 'Max': 'max'
31
+ }
32
+ CURRENCIES = ["USD", "EUR", "GBP", "JPY", "AUD"]
33
+
34
+ # Initialize pygame for music
35
+ def initialize_music():
36
+ if 'music_initialized' not in st.session_state:
37
+ try:
38
+ pygame.mixer.init()
39
+ pygame.mixer.music.load('trade.mp3')
40
+ pygame.mixer.music.play(-1)
41
+ st.session_state['music_initialized'] = True
42
+ except pygame.error as e:
43
+ st.error(f"Failed to load or play the music: {str(e)}. Please check that 'trade.mp3' exists in the correct directory.")
44
+
45
+ # Database setup
46
+ def setup_database():
47
+ conn = sqlite3.connect('trading_game.db', check_same_thread=False)
48
+ cursor = conn.cursor()
49
+
50
+ # Create tables if they don't exist
51
+ cursor.execute('''
52
+ CREATE TABLE IF NOT EXISTS users (
53
+ username TEXT PRIMARY KEY,
54
+ password TEXT,
55
+ balance REAL,
56
+ initial_balance REAL,
57
+ currency TEXT DEFAULT 'USD'
58
+ )
59
+ ''')
60
+
61
+ cursor.execute('''
62
+ CREATE TABLE IF NOT EXISTS portfolios (
63
+ username TEXT,
64
+ ticker TEXT,
65
+ shares REAL,
66
+ initial_investment REAL,
67
+ PRIMARY KEY (username, ticker),
68
+ FOREIGN KEY (username) REFERENCES users (username)
69
+ )
70
+ ''')
71
+
72
+ cursor.execute('''
73
+ CREATE TABLE IF NOT EXISTS financial_logs (
74
+ username TEXT,
75
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
76
+ total_value REAL,
77
+ FOREIGN KEY (username) REFERENCES users (username)
78
+ )
79
+ ''')
80
+
81
+ cursor.execute('''
82
+ CREATE TABLE IF NOT EXISTS transactions (
83
+ username TEXT,
84
+ action TEXT,
85
+ ticker TEXT,
86
+ amount REAL,
87
+ price REAL,
88
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
89
+ )
90
+ ''')
91
+
92
+ conn.commit()
93
+ return conn
94
+
95
+ # Ensure Database Schema
96
+ def ensure_database_schema():
97
+ conn = sqlite3.connect('trading_game.db')
98
+ c = conn.cursor()
99
+
100
+ # Ensure the users table exists and has the required columns
101
+ c.execute('''
102
+ CREATE TABLE IF NOT EXISTS users (
103
+ username TEXT PRIMARY KEY,
104
+ password TEXT,
105
+ balance REAL,
106
+ initial_balance REAL,
107
+ currency TEXT DEFAULT 'USD'
108
+ )
109
+ ''')
110
+
111
+ # Check if the initial_balance column exists, if not, add it
112
+ c.execute("PRAGMA table_info(users)")
113
+ columns = [column[1] for column in c.fetchall()]
114
+ if 'initial_balance' not in columns:
115
+ c.execute(f"ALTER TABLE users ADD COLUMN initial_balance REAL DEFAULT {INITIAL_BALANCE}")
116
+ conn.commit()
117
+
118
+ # Ensure the portfolios table exists and has the required columns
119
+ c.execute('''
120
+ CREATE TABLE IF NOT EXISTS portfolios (
121
+ username TEXT,
122
+ ticker TEXT,
123
+ shares REAL,
124
+ initial_investment REAL,
125
+ PRIMARY KEY (username, ticker),
126
+ FOREIGN KEY (username) REFERENCES users (username)
127
+ )
128
+ ''')
129
+
130
+ # Check if the initial_investment column exists, if not, add it
131
+ c.execute("PRAGMA table_info(portfolios)")
132
+ columns = [column[1] for column in c.fetchall()]
133
+ if 'initial_investment' not in columns:
134
+ c.execute(f"ALTER TABLE portfolios ADD COLUMN initial_investment REAL")
135
+ conn.commit()
136
+
137
+ conn.close()
138
+
139
+ # Function to hash passwords
140
+ def hash_password(password):
141
+ return hashlib.sha256(password.encode()).hexdigest()
142
+
143
+ # Function to set the background image
144
+ def set_background_image(image_path):
145
+ if Path(image_path).exists():
146
+ try:
147
+ with open(image_path, "rb") as img_file:
148
+ encoded_img = base64.b64encode(img_file.read()).decode('utf-8')
149
+ st.markdown(
150
+ f"""
151
+ <style>
152
+ .stApp {{
153
+ position: relative;
154
+ background-image: url(data:image/png;base64,{encoded_img});
155
+ background-size: cover;
156
+ background-position: center;
157
+ background-repeat: no-repeat;
158
+ }}
159
+ .stApp::before {{
160
+ content: '';
161
+ position: absolute;
162
+ top: 0;
163
+ left: 0;
164
+ right: 0;
165
+ bottom: 0;
166
+ background-color: rgba(255, 255, 255, 0.8); /* White overlay with 0.8 opacity */
167
+ z-index: 0;
168
+ }}
169
+ .main-title, .sub-title, .metric-box, .blue-background {{
170
+ position: relative;
171
+ z-index: 1;
172
+ }}
173
+ </style>
174
+ """,
175
+ unsafe_allow_html=True
176
+ )
177
+ except Exception as e:
178
+ st.error(f"Error loading image: {str(e)}")
179
+ else:
180
+ st.error('Image not found. Please check the file name and path.')
181
+
182
+ # Function to fetch the current price of stocks or currencies
183
+ def get_stock_data(ticker, period='1d'):
184
+ try:
185
+ stock = Ticker(ticker)
186
+ df = stock.history(period=period)
187
+ if not df.empty:
188
+ df.reset_index(inplace=True)
189
+ return df
190
+ else:
191
+ return None
192
+ except Exception as e:
193
+ print(f"Error fetching data: {str(e)}")
194
+ return None
195
+
196
+ # Function to convert currency
197
+ def convert_currency(amount, from_currency, to_currency):
198
+ if from_currency == to_currency:
199
+ return amount
200
+ try:
201
+ response = requests.get(f'https://api.exchangerate-api.com/v4/latest/{from_currency}')
202
+ rates = response.json()['rates']
203
+ conversion_rate = rates.get(to_currency, 1)
204
+ return amount * conversion_rate
205
+ except Exception as e:
206
+ print(f"Error converting currency: {str(e)}")
207
+ return amount
208
+
209
+ # Function to get market status
210
+ def get_market_status():
211
+ market_status = {}
212
+ for ticker in STOCK_LIST:
213
+ df = get_stock_data(ticker, period='1d')
214
+ if df is not None:
215
+ open_price = df['Open'].iloc[0]
216
+ current_price = df['Close'].iloc[-1]
217
+ change = (current_price - open_price) / open_price * 100
218
+ market_status[ticker] = change
219
+ return market_status
220
+
221
+ # Function to fetch and plot historical data for holdings
222
+ def plot_holdings(holdings, time_range):
223
+ fig = px.line(title=f"Holdings - {time_range}")
224
+ for ticker in holdings:
225
+ df = get_stock_data(ticker, period=TIME_RANGES[time_range])
226
+ if df is not None and not df.empty:
227
+ initial_investment = holdings[ticker]['initial_investment']
228
+ fig.add_scatter(x=df['Date'], y=df['Close'], mode='lines', name=f"{ticker} (Invested: ${initial_investment:.2f})")
229
+ st.plotly_chart(fig, use_container_width=True)
230
+
231
+ # Function to display the market overview ticker
232
+ def market_overview_ticker(market_status):
233
+ ticker_text = ''.join([
234
+ f"<span style='color: {'green' if change > 0 else 'red'};'>{ticker}: {change:.2f}%</span> | "
235
+ for ticker, change in market_status.items()
236
+ ])
237
+ st.markdown(f"""
238
+ <style>
239
+ .ticker {{
240
+ overflow: hidden;
241
+ white-space: nowrap;
242
+ width: 100%;
243
+ background-color: black;
244
+ color: white;
245
+ padding: 5px;
246
+ font-size: 14px;
247
+ }}
248
+ .ticker span {{
249
+ display: inline-block;
250
+ animation: ticker 20s linear infinite;
251
+ }}
252
+ @keyframes ticker {{
253
+ 0% {{ transform: translateX(100%); }}
254
+ 100% {{ transform: translateX(-100%); }}
255
+ }}
256
+ </style>
257
+ <div class="ticker">
258
+ <span>{ticker_text}</span>
259
+ </div>
260
+ """, unsafe_allow_html=True)
261
+
262
+ # Function to fetch currency values
263
+ def get_currency_values():
264
+ currencies = ['EUR', 'GBP', 'JPY', 'AUD']
265
+ currency_data = {}
266
+ for currency in currencies:
267
+ rate = convert_currency(1, 'USD', currency)
268
+ currency_data[currency] = rate
269
+ return currency_data
270
+
271
+ # Function to fetch gold price from yfinance
272
+ def get_gold_price():
273
+ gold_ticker = Ticker("GC=F")
274
+ df = gold_ticker.history(period="1d")
275
+ if not df.empty:
276
+ return df['Close'].iloc[-1]
277
+ else:
278
+ st.error("Failed to fetch the goldprice.")
279
+ return None
280
+
281
+ # Function to display currency and gold values
282
+ def display_currency_and_gold():
283
+ st.markdown('<div class="sub-title">Currency and Gold Values</div>', unsafe_allow_html=True)
284
+ currency_data = get_currency_values()
285
+ gold_price = get_gold_price()
286
+ for currency, value in currency_data.items():
287
+ st.markdown(f'<div class="metric-box">{currency}/USD: {value:.2f}</div>', unsafe_allow_html=True)
288
+ if gold_price:
289
+ st.markdown(f'<div class="metric-box">Gold/USD: ${gold_price:.2f}</div>', unsafe_allow_html=True)
290
+
291
+ # Function to display the user's financial overview
292
+ def display_financial_overview(username, conn):
293
+ st.subheader("Your Financial Overview")
294
+ total_value, invested_value, initial_balance = calculate_total_value(username, conn)
295
+ st.markdown(f'<div class="metric-box">Total Value (Including Investments): ${total_value:.2f}</div>', unsafe_allow_html=True)
296
+ st.markdown(f'<div class="metric-box">Invested Value: ${invested_value:.2f}</div>', unsafe_allow_html=True)
297
+ st.markdown(f'<div class="metric-box">Initial Investment: ${initial_balance:.2f}</div>', unsafe_allow_html=True)
298
+
299
+ # Reintroducing get_user_data with initial_investment
300
+ def get_user_data(username, conn):
301
+ c = conn.cursor()
302
+ c.execute("SELECT balance, currency, initial_balance FROM users WHERE username = ?", (username,))
303
+ balance, currency, initial_balance = c.fetchone()
304
+
305
+ c.execute("SELECT ticker, shares, initial_investment FROM portfolios WHERE username = ?", (username,))
306
+ portfolio_data = c.fetchall()
307
+ portfolio = {ticker: {"shares": shares, "initial_investment": initial_investment} for ticker, shares, initial_investment in portfolio_data}
308
+
309
+ return balance, currency, initial_balance, portfolio
310
+
311
+ # Function to calculate total value with comparison to initial investment
312
+ def calculate_total_value(username, conn):
313
+ balance, currency, initial_balance, portfolio = get_user_data(username, conn)
314
+ total_value = balance
315
+ invested_value = 0
316
+ for ticker, data in portfolio.items():
317
+ if data['shares'] > 0:
318
+ stock_data = get_stock_data(ticker, '1d')
319
+ if stock_data is not None:
320
+ last_close = stock_data['Close'].iloc[-1]
321
+ invested_value += last_close * data['shares']
322
+ total_value += invested_value
323
+ total_value = convert_currency(total_value, 'USD', currency)
324
+ invested_value = convert_currency(invested_value, 'USD', currency)
325
+ initial_balance_converted = convert_currency(initial_balance, 'USD', currency)
326
+ return total_value, invested_value, initial_balance_converted
327
+
328
+ # Function to buy stock
329
+ def buy_stock(username, ticker, amount, conn, currency):
330
+ current_price = get_stock_data(ticker, period='1d')
331
+
332
+ if current_price is None or current_price empty:
333
+ return "Invalid ticker symbol."
334
+
335
+ total_cost = current_price['Close'].iloc[-1] * amount
336
+
337
+ c = conn.cursor()
338
+ c.execute("SELECT balance, currency FROM users WHERE username = ?", (username,))
339
+ user_balance, user_currency = c.fetchone()
340
+
341
+ total_cost_in_user_currency = convert_currency(total_cost, 'USD', user_currency)
342
+
343
+ if user_balance < total_cost_in_user_currency:
344
+ return "Insufficient funds."
345
+
346
+ new_balance = user_balance - total_cost_in_user_currency
347
+ c.execute("UPDATE users SET balance = ? WHERE username = ?", (new_balance, username))
348
+
349
+ c.execute("SELECT shares FROM portfolios WHERE username = ? AND ticker = ?", (username, ticker))
350
+ existing_shares = c.fetchone()
351
+
352
+ if existing_shares:
353
+ new_shares = existing_shares[0] + amount
354
+ c.execute("UPDATE portfolios SET shares = ?, initial_investment = initial_investment + ? WHERE username = ? AND ticker = ?", (new_shares, total_cost, username, ticker))
355
+ else:
356
+ c.execute("INSERT INTO portfolios (username, ticker, shares, initial_investment) VALUES (?, ?, ?, ?)", (username, ticker, amount, total_cost))
357
+
358
+ c.execute("INSERT INTO transactions (username, action, ticker, amount, price) VALUES (?, ?, ?, ?, ?)",
359
+ (username, 'Buy', ticker, amount, current_price['Close'].iloc[-1]))
360
+
361
+ conn.commit()
362
+ return f"Successfully bought {amount} shares of {ticker} for ${total_cost_in_user_currency:.2f} {user_currency}"
363
+
364
+ # Function to sell stock
365
+ def sell_stock(username, ticker, amount, conn, currency):
366
+ current_price = get_stock_data(ticker, period='1d')
367
+
368
+ if current_price is None or current_price empty:
369
+ return "Invalid ticker symbol."
370
+
371
+ total_value = current_price['Close'].iloc[-1] * amount
372
+
373
+ c = conn.cursor()
374
+ c.execute("SELECT shares FROM portfolios WHERE username = ? AND ticker = ?", (username, ticker))
375
+ existing_shares = c.fetchone()
376
+
377
+ if not existing_shares or existing_shares[0] < amount:
378
+ return "Insufficient shares to sell."
379
+
380
+ new_shares = existing_shares[0] - amount
381
+ if new_shares == 0:
382
+ c.execute("DELETE FROM portfolios WHERE username = ? AND ticker = ?", (username, ticker))
383
+ else:
384
+ c.execute("UPDATE portfolios SET shares = ? WHERE username = ? AND ticker = ?", (new_shares, username, ticker))
385
+
386
+ c.execute("SELECT balance, currency FROM users WHERE username = ?", (username,))
387
+ user_balance, user_currency = c.fetchone()
388
+ total_value_in_user_currency = convert_currency(total_value, 'USD', user_currency)
389
+ new_balance = user_balance + total_value_in_user_currency
390
+ c.execute("UPDATE users SET balance = ? WHERE username = ?", (new_balance, username))
391
+
392
+ c.execute("INSERT INTO transactions (username, action, ticker, amount, price) VALUES (?, ?, ?, ?, ?)",
393
+ (username, 'Sell', ticker, amount, current_price['Close'].iloc[-1]))
394
+
395
+ conn.commit()
396
+ return f"Successfully sold {amount} shares of {ticker} for ${total_value_in_user_currency:.2f} {user_currency}"
397
+
398
+ # Main function with enhanced layout and features
399
+ def main():
400
+ ensure_database_schema()
401
+ initialize_music()
402
+
403
+ if 'logged_in' not in st.session_state:
404
+ st.session_state.logged_in = False
405
+ if 'username' not in st.session_state:
406
+ st.session_state.username = None
407
+
408
+ st.markdown('<div class="main-title" style="background-color: blue; color: white; padding: 10px;">Charging Bull Trader</div>', unsafe_allow_html=True)
409
+
410
+ if not st.session_state.logged_in:
411
+ st.markdown('<div class="sub-title">Login</div>', unsafe_allow_html=True)
412
+ username_input = st.text_input("Username")
413
+ password_input = st.text_input("Password", type="password")
414
+ if st.button("Login"):
415
+ conn = setup_database()
416
+ c = conn.cursor()
417
+ c.execute("SELECT * FROM users WHERE username=? AND password=?", (username_input, hash_password(password_input)))
418
+ if c.fetchone():
419
+ st.session_state.logged_in = True
420
+ st.session_state.username = username_input
421
+ else:
422
+ st.error("Invalid username or password")
423
+
424
+ if st.button("Create Account"):
425
+ conn = setup_database()
426
+ c = conn.cursor()
427
+ try:
428
+ c.execute("INSERT INTO users (username, password, balance, initial_balance, currency) VALUES (?, ?, ?, ?, ?)",
429
+ (username_input, hash_password(password_input), INITIAL_BALANCE, INITIAL_BALANCE, DEFAULT_CURRENCY))
430
+ for ticker in STOCK_LIST:
431
+ c.execute("INSERT INTO portfolios (username, ticker, shares, initial_investment) VALUES (?, ?, 0, 0)", (username_input, ticker))
432
+ conn.commit()
433
+ st.success("Account created successfully!")
434
+ except sqlite3.IntegrityError:
435
+ st.error("Username already exists.")
436
+ else:
437
+ username = st.session_state.username
438
+ conn = setup_database()
439
+ st.sidebar.write(f"Welcome, {username}!")
440
+ if st.sidebar.button("Logout"):
441
+ st.session_state.logged_in = False
442
+ st.session_state.username = None
443
+
444
+ # Add the small Charging Bull image under the Logout button
445
+ st.sidebar.image("trade.png", width=150)
446
+
447
+ display_financial_overview(username, conn)
448
+
449
+ col1, col2 = st.columns(2)
450
+
451
+ with col1:
452
+ st.markdown('<div class="sub-title">Personal Holdings and Stocks</div>', unsafe_allow_html=True)
453
+ time_range = st.selectbox("Select Time Range for Holdings", list(TIME_RANGES.keys()))
454
+ _, _, _, portfolio = get_user_data(username, conn)
455
+ if portfolio:
456
+ plot_holdings(portfolio, time_range)
457
+ else:
458
+ st.write("No stocks in your portfolio.")
459
+
460
+ st.markdown('<div class="sub-title">Buy/Sell Stocks</div>', unsafe_allow_html=True)
461
+ action = st.selectbox("Choose Action", ["Buy", "Sell"])
462
+ ticker = st.text_input("Ticker")
463
+ amount = st.number_input("Amount", min_value=1, step=1)
464
+ selected_currency = st.selectbox("Currency", CURRENCIES)
465
+
466
+ if st.button(f"{action} Stocks"):
467
+ if action == "Buy":
468
+ result = buy_stock(username, ticker, amount, conn, selected_currency)
469
+ else:
470
+ result = sell_stock(username, ticker, amount, conn, selected_currency)
471
+ st.write(result)
472
+
473
+ with col2:
474
+ market_status = get_market_status()
475
+ ```python
476
+ market_overview_ticker(market_status)
477
+ display_currency_and_gold()
478
+
479
+ st.markdown('<div class="sub-title">Stock Lookup</div>', unsafe_allow_html=True)
480
+ stock_ticker = st.text_input("Enter Stock Ticker for Lookup")
481
+ time_range_lookup = st.selectbox("Select Time Range", list(TIME_RANGES.keys()), key="lookup")
482
+ if st.button("Lookup"):
483
+ df = get_stock_data(stock_ticker, period=TIME_RANGES[time_range_lookup])
484
+ if df is not None and not df.empty:
485
+ fig = px.line(df, x='Date', y='Close', title=f"{stock_ticker} - {time_range_lookup}")
486
+ st.plotly_chart(fig, use_container_width=True)
487
+ else:
488
+ st.write("No data available for this ticker.")
489
+
490
+ if __name__ == "__main__":
491
+ main()