Skip to content

Commit 767663b

Browse files
ProSureStringCyteon
authored andcommitted
fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
fix this shit code pls
1 parent d94fb3f commit 767663b

File tree

3 files changed

+319
-0
lines changed

3 files changed

+319
-0
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ FUSION_API_KEY=
1313
FUSION_SECRET_KEY=
1414
TENOR_API_KEY=
1515
HASHING_SECRET="changeme" # change this to a random string
16+
ALPHA_VANTAGE_API_KEY=
1617

1718
LAVALINK_HOST="0.0.0.0"
1819
LAVALINK_PORT=1234

cogs/economy.py

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from ui.farm import FarmButton
1313
from ui.gambling import GamblingButton
1414

15+
from ui.papertrading import start_paper_trading
16+
1517
db = DBClient.db
1618

1719
class Economy(commands.Cog, name="🪙 Economy"):
@@ -293,6 +295,16 @@ async def gamble(self, context: Context, amount: int) -> None:
293295
view=GamblingButton(amount, context.author.id),
294296
)
295297

298+
@commands.hybrid_command(
299+
name="stockmarket",
300+
description="Gamble your money(but like irl but like fake fr)",
301+
usage="stockmarket"
302+
)
303+
@commands.check(Checks.is_not_blacklisted)
304+
@commands.check(Checks.command_not_disabled)
305+
async def stockmarket(self, context: Context) -> None:
306+
await start_paper_trading(context)
307+
296308
# TODO: MORE CACHING AFTER THIS POINT
297309

298310
@commands.hybrid_command(

ui/papertrading.py

+306
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import discord
2+
import asyncio
3+
import aiohttp
4+
import json
5+
from datetime import datetime
6+
from discord import ui
7+
from discord.ui import Button, button, View
8+
9+
from utils import DBClient
10+
11+
db = DBClient.db
12+
13+
# Configuration Constants
14+
# TODO: put this in a config file
15+
ALPHA_VANTAGE_API_KEY = os.getenv('ALPHA_VANTAGE_API_KEY')
16+
MIN_TRADE_AMOUNT = 1
17+
MAX_TRADE_AMOUNT = 1000
18+
TRADE_COOLDOWN = 5
19+
DEMO_MODE = True
20+
MOCK_PRICES = {
21+
"AAPL": 175.50,
22+
"GOOGL": 140.25,
23+
"MSFT": 380.75,
24+
"AMZN": 145.30,
25+
"TSLA": 240.45,
26+
"META": 485.60,
27+
"NVDA": 820.30,
28+
"AMD": 175.25
29+
}
30+
31+
class StockPortfolioView(View):
32+
def __init__(self, authorid):
33+
super().__init__(timeout=None)
34+
self.authorid = authorid
35+
self.last_trade_time = {}
36+
37+
@button(label="Buy Stocks", style=discord.ButtonStyle.primary, custom_id="buy_stocks", emoji="📈")
38+
async def buy_stocks(self, interaction: discord.Interaction, button: discord.ui.Button):
39+
if interaction.user.id != self.authorid:
40+
return await interaction.response.send_message("This isn't your trading session!", ephemeral=True)
41+
42+
current_time = datetime.now().timestamp()
43+
if self.authorid in self.last_trade_time:
44+
time_diff = current_time - self.last_trade_time[self.authorid]
45+
if time_diff < TRADE_COOLDOWN:
46+
return await interaction.response.send_message(
47+
f"Please wait {TRADE_COOLDOWN - int(time_diff)} seconds before trading again!",
48+
ephemeral=True
49+
)
50+
51+
self.last_trade_time[self.authorid] = current_time
52+
await interaction.response.send_modal(BuyStocksModal(self.authorid))
53+
54+
@button(label="Sell Stocks", style=discord.ButtonStyle.danger, custom_id="sell_stocks", emoji="📉")
55+
async def sell_stocks(self, interaction: discord.Interaction, button: discord.ui.Button):
56+
if interaction.user.id != self.authorid:
57+
return await interaction.response.send_message("This isn't your trading session!", ephemeral=True)
58+
59+
current_time = datetime.now().timestamp()
60+
if self.authorid in self.last_trade_time:
61+
time_diff = current_time - self.last_trade_time[self.authorid]
62+
if time_diff < TRADE_COOLDOWN:
63+
return await interaction.response.send_message(
64+
f"Please wait {TRADE_COOLDOWN - int(time_diff)} seconds before trading again!",
65+
ephemeral=True
66+
)
67+
68+
self.last_trade_time[self.authorid] = current_time
69+
await interaction.response.send_modal(SellStocksModal(self.authorid))
70+
71+
@button(label="View Portfolio", style=discord.ButtonStyle.secondary, custom_id="view_portfolio", emoji="📊")
72+
async def view_portfolio(self, interaction: discord.Interaction, button: discord.ui.Button):
73+
if interaction.user.id != self.authorid:
74+
return await interaction.response.send_message("This isn't your trading session!", ephemeral=True)
75+
76+
c = db["trading"]
77+
portfolio = c.find_one({"user_id": interaction.user.id, "guild_id": interaction.guild.id})
78+
79+
if not portfolio or not portfolio.get("positions", {}):
80+
return await interaction.response.send_message("You don't have any positions yet!", ephemeral=True)
81+
82+
c_users = db["users"]
83+
user = c_users.find_one({"id": interaction.user.id, "guild_id": interaction.guild.id})
84+
85+
embed = discord.Embed(title="Your Portfolio", color=0x00ff00)
86+
embed.add_field(name="Available Balance", value=f"${user['wallet']:,.2f}", inline=False)
87+
88+
total_value = 0
89+
90+
for symbol, position in portfolio["positions"].items():
91+
price = await get_stock_price(symbol)
92+
if price:
93+
current_value = position["shares"] * price
94+
total_value += current_value
95+
profit_loss = current_value - (position["shares"] * position["average_price"])
96+
97+
embed.add_field(
98+
name=f"{symbol}",
99+
value=f"Shares: {position['shares']}\n"
100+
f"Avg Price: ${position['average_price']:.2f}\n"
101+
f"Current Price: ${price:.2f}\n"
102+
f"P/L: ${profit_loss:.2f} ({(profit_loss/current_value)*100:.1f}%)",
103+
inline=False
104+
)
105+
106+
embed.add_field(name="Total Portfolio Value", value=f"${total_value:.2f}", inline=False)
107+
embed.add_field(name="Total Account Value", value=f"${(total_value + user['wallet']):.2f}", inline=False)
108+
await interaction.response.send_message(embed=embed, ephemeral=True)
109+
110+
class BuyStocksModal(ui.Modal, title="Buy Stocks"):
111+
def __init__(self, authorid):
112+
super().__init__()
113+
self.authorid = authorid
114+
115+
symbol = ui.TextInput(label="Stock Symbol", placeholder="e.g. AAPL", min_length=1, max_length=5)
116+
shares = ui.TextInput(label="Number of Shares", placeholder="e.g. 10")
117+
118+
async def on_submit(self, interaction: discord.Interaction):
119+
if interaction.user.id != self.authorid:
120+
return await interaction.response.send_message("This isn't your trading session!", ephemeral=True)
121+
122+
symbol = self.symbol.value.upper()
123+
try:
124+
shares = float(self.shares.value)
125+
if not MIN_TRADE_AMOUNT <= shares <= MAX_TRADE_AMOUNT:
126+
return await interaction.response.send_message(
127+
f"Please enter between {MIN_TRADE_AMOUNT} and {MAX_TRADE_AMOUNT} shares!",
128+
ephemeral=True
129+
)
130+
except ValueError:
131+
return await interaction.response.send_message("Please enter a valid number of shares!", ephemeral=True)
132+
133+
price = await get_stock_price(symbol)
134+
if not price:
135+
return await interaction.response.send_message("Invalid stock symbol or API error!", ephemeral=True)
136+
137+
total_cost = price * shares
138+
139+
c = db["users"]
140+
user = c.find_one({"id": interaction.user.id, "guild_id": interaction.guild.id})
141+
if not user or user["wallet"] < total_cost:
142+
return await interaction.response.send_message(
143+
f"Insufficient funds! You need ${total_cost:,.2f} but have ${user['wallet']:,.2f}",
144+
ephemeral=True
145+
)
146+
147+
c = db["trading"]
148+
portfolio = c.find_one({"user_id": interaction.user.id, "guild_id": interaction.guild.id})
149+
150+
if not portfolio:
151+
portfolio = {
152+
"user_id": interaction.user.id,
153+
"guild_id": interaction.guild.id,
154+
"positions": {}
155+
}
156+
c.insert_one(portfolio)
157+
158+
if symbol in portfolio["positions"]:
159+
current_position = portfolio["positions"][symbol]
160+
new_shares = current_position["shares"] + shares
161+
new_average_price = ((current_position["shares"] * current_position["average_price"]) + total_cost) / new_shares
162+
portfolio["positions"][symbol] = {
163+
"shares": new_shares,
164+
"average_price": new_average_price
165+
}
166+
else:
167+
portfolio["positions"][symbol] = {
168+
"shares": shares,
169+
"average_price": price
170+
}
171+
172+
c.update_one(
173+
{"user_id": interaction.user.id, "guild_id": interaction.guild.id},
174+
{"$set": {"positions": portfolio["positions"]}}
175+
)
176+
177+
user["wallet"] -= total_cost
178+
c = db["users"]
179+
c.update_one(
180+
{"id": interaction.user.id, "guild_id": interaction.guild.id},
181+
{"$set": {"wallet": user["wallet"]}}
182+
)
183+
184+
await interaction.response.send_message(
185+
f"Successfully bought {shares} shares of {symbol} at ${price:.2f} per share.\n"
186+
f"Total cost: ${total_cost:.2f}\n"
187+
f"Remaining balance: ${user['wallet']:,.2f}",
188+
ephemeral=True
189+
)
190+
191+
class SellStocksModal(ui.Modal, title="Sell Stocks"):
192+
def __init__(self, authorid):
193+
super().__init__()
194+
self.authorid = authorid
195+
196+
symbol = ui.TextInput(label="Stock Symbol", placeholder="e.g. AAPL", min_length=1, max_length=5)
197+
shares = ui.TextInput(label="Number of Shares", placeholder="e.g. 10")
198+
199+
async def on_submit(self, interaction: discord.Interaction):
200+
if interaction.user.id != self.authorid:
201+
return await interaction.response.send_message("This isn't your trading session!", ephemeral=True)
202+
203+
symbol = self.symbol.value.upper()
204+
try:
205+
shares = float(self.shares.value)
206+
if shares <= 0:
207+
raise ValueError("Shares must be positive")
208+
except ValueError:
209+
return await interaction.response.send_message("Please enter a valid number of shares!", ephemeral=True)
210+
211+
c = db["trading"]
212+
portfolio = c.find_one({"user_id": interaction.user.id, "guild_id": interaction.guild.id})
213+
214+
if not portfolio or symbol not in portfolio["positions"]:
215+
return await interaction.response.send_message("You don't own this stock!", ephemeral=True)
216+
217+
current_position = portfolio["positions"][symbol]
218+
if current_position["shares"] < shares:
219+
return await interaction.response.send_message(
220+
f"You don't have enough shares! You own {current_position['shares']} shares.",
221+
ephemeral=True
222+
)
223+
224+
price = await get_stock_price(symbol)
225+
if not price:
226+
return await interaction.response.send_message("Invalid stock symbol or API error!", ephemeral=True)
227+
228+
total_value = price * shares
229+
230+
new_shares = current_position["shares"] - shares
231+
if new_shares == 0:
232+
del portfolio["positions"][symbol]
233+
else:
234+
portfolio["positions"][symbol]["shares"] = new_shares
235+
236+
c.update_one(
237+
{"user_id": interaction.user.id, "guild_id": interaction.guild.id},
238+
{"$set": {"positions": portfolio["positions"]}}
239+
)
240+
241+
c = db["users"]
242+
user = c.find_one({"id": interaction.user.id, "guild_id": interaction.guild.id})
243+
user["wallet"] += total_value
244+
c.update_one(
245+
{"id": interaction.user.id, "guild_id": interaction.guild.id},
246+
{"$set": {"wallet": user["wallet"]}}
247+
)
248+
249+
profit_loss = (price - current_position["average_price"]) * shares
250+
251+
await interaction.response.send_message(
252+
f"Successfully sold {shares} shares of {symbol} at ${price:.2f} per share.\n"
253+
f"Total value: ${total_value:.2f}\n"
254+
f"Profit/Loss: ${profit_loss:.2f}\n"
255+
f"New balance: ${user['wallet']:,.2f}",
256+
ephemeral=True
257+
)
258+
259+
async def get_stock_price(symbol):
260+
"""Get current stock price using Alpha Vantage API or mock data"""
261+
if DEMO_MODE and symbol in MOCK_PRICES:
262+
return MOCK_PRICES[symbol]
263+
264+
url = f"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={ALPHA_VANTAGE_API_KEY}"
265+
266+
async with aiohttp.ClientSession() as session:
267+
try:
268+
async with session.get(url) as response:
269+
data = await response.json()
270+
if "Global Quote" in data and "05. price" in data["Global Quote"]:
271+
return float(data["Global Quote"]["05. price"])
272+
return None
273+
except:
274+
return None
275+
276+
async def start_paper_trading(ctx):
277+
"""
278+
Command to start paper trading session
279+
Usage: !trade or /trade
280+
"""
281+
c = db["users"]
282+
user = c.find_one({"id": ctx.author.id, "guild_id": ctx.guild.id})
283+
284+
if not user:
285+
return await ctx.send("get outa here brokie")
286+
287+
embed = discord.Embed(
288+
title="Paper Trading",
289+
description="Welcome to paper trading! Trade stocks with your existing balance.\n"
290+
"Use the buttons below to buy/sell stocks and view your portfolio.",
291+
color=0x00ff00
292+
)
293+
embed.add_field(
294+
name="Available Balance",
295+
value=f"${user['wallet']:,.2f}",
296+
inline=False
297+
)
298+
if DEMO_MODE:
299+
embed.add_field(
300+
name="Available Demo Stocks",
301+
value="\n".join([f"{symbol}: ${price:.2f}" for symbol, price in MOCK_PRICES.items()]),
302+
inline=False
303+
)
304+
305+
view = StockPortfolioView(ctx.author.id)
306+
await ctx.send(embed=embed, view=view)

0 commit comments

Comments
 (0)