Skip to content

Commit 98efc74

Browse files
author
Caleb Quilley
committed
Add qty fields to market orders
1 parent 5ae5579 commit 98efc74

File tree

1 file changed

+58
-43
lines changed

1 file changed

+58
-43
lines changed

cogs/commands/market.py

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import heapq
22
import time
3+
from collections import defaultdict
34

45
from discord.ext import commands
56
from discord.ext.commands import Bot, Context, check, clean_content
@@ -14,10 +15,11 @@
1415

1516

1617
class Order:
17-
def __init__(self, price, order_type, user_id):
18+
def __init__(self, price, order_type, user_id, qty):
1819
self.user_id = user_id
1920
self.price = price
2021
self.order_type = order_type
22+
self.qty = qty
2123
self.order_time = time.time()
2224

2325
def __lt__(self, other):
@@ -50,23 +52,25 @@ def __init__(self, stock_name):
5052
self.last_trade = None
5153
self.open = True
5254

53-
def bid(self, price, user_id):
54-
self.bids.append(Order(price, 'bid', user_id))
55+
def bid(self, price, user_id, qty):
56+
self.bids.append(Order(price, 'bid', user_id, qty))
5557
heapq.heapify(self.bids)
5658
return self.match()
5759

58-
def ask(self, price, user_id):
59-
self.asks.append(Order(price, 'ask', user_id))
60+
def ask(self, price, user_id, qty):
61+
self.asks.append(Order(price, 'ask', user_id, qty))
6062
heapq.heapify(self.asks)
6163
return self.match()
6264

6365
def match(self):
6466
if len(self.bids) == 0 or len(self.asks) == 0:
6567
return None
6668

67-
if self.bids[0].price >= self.asks[0].price:
69+
matched = []
70+
while self.bids[0].price >= self.asks[0].price:
6871
bid = heapq.heappop(self.bids)
6972
ask = heapq.heappop(self.asks)
73+
qty = min(bid.qty, ask.qty)
7074

7175
if bid.user_id not in self.trade_history:
7276
self.trade_history[bid.user_id] = []
@@ -78,31 +82,34 @@ def match(self):
7882

7983
bid.price = earliest_trade.price
8084
ask.price = earliest_trade.price
85+
86+
bought = Order(earliest_trade.price, 'bid', bid.price, qty)
87+
sold = Order(earliest_trade.price, 'ask', ask.user_id, qty)
8188

82-
self.trade_history[bid.user_id].append(bid)
83-
self.trade_history[ask.user_id].append(ask)
84-
85-
self.last_trade = f"<@{bid.user_id}> bought from <@{ask.user_id}> at {bid.price}"
89+
self.trade_history[bid.user_id].append(bought)
90+
self.trade_history[ask.user_id].append(sold)
8691

87-
return self.last_trade
88-
return None
92+
self.last_trade = f"<@{bid.user_id}> bought {qty} from <@{ask.user_id}> at {bid.price}"
93+
94+
if ask.qty > qty:
95+
heapq.heappush(self.asks, Order(ask.price, 'ask', ask.user_id, ask.qty - qty))
96+
elif bid.qty > qty:
97+
heapq.heappush(self.bids, Order(bid.price, 'bid', bid.user_id, bid.qty - qty))
98+
99+
matched.append(self.last_trade)
100+
101+
return "\n".join(matched) if len(matched) > 0 else None
89102

90103

91104

92105
def close_market(self, valuation):
93106
user_to_profit = {}
94107
for user in self.trade_history:
95-
user_valuation = 0
96-
for trade in self.trade_history[user]:
97-
if trade.order_type == 'bid':
98-
user_valuation -= trade.price
99-
user_valuation += valuation
100-
else:
101-
user_valuation += trade.price
102-
user_valuation -= valuation
103-
104-
user_to_profit[user] = user_valuation
105-
108+
closing = valuation * sum(trade.qty if trade.order_type == 'bid' else -trade.qty for trade in self.trade_history[user])
109+
# Note: accumulating _value_ not position, so signs are reversed
110+
pnl = sum(trade.price * (trade.qty if trade.order_type == 'ask' else -trade.qty) for trade in self.trade_history[user])
111+
user_to_profit[user] = closing + pnl
112+
106113
self.open = False
107114

108115
return user_to_profit
@@ -135,13 +142,17 @@ def __str__(self):
135142
ret_str = "Market is: "
136143
ret_str += "OPEN\n\n" if self.open else "CLOSED\n\n"
137144

138-
# Count bids and asks for each price level
139-
bid_counts = {}
140-
ask_counts = {}
145+
# Count bids and asks and sum quantity for each price level
146+
bid_counts = defaultdict(lambda: [0,0])
147+
ask_counts = defaultdict(lambda: [0,0])
141148
for bid in self.bids:
142-
bid_counts[bid.price] = bid_counts.get(bid.price, 0) + 1
149+
level = bid_counts[bid.price]
150+
level[0] += 1
151+
level[1] += bid.qty
143152
for ask in self.asks:
144-
ask_counts[ask.price] = ask_counts.get(ask.price, 0) + 1
153+
level = ask_counts[ask.price]
154+
level[0] += 1
155+
level[1] += ask.qty
145156

146157
# Get price levels; highest first
147158
all_prices = sorted(set(bid_counts.keys()).union(set(ask_counts.keys())), reverse=True)
@@ -152,13 +163,13 @@ def __str__(self):
152163
order_book_lines.append("No outstanding orders\n")
153164
else:
154165
order_book_lines.append("```")
155-
order_book_lines.append(f"{'Bid Volume':<15} | {'Price':<10} | {'Ask Volume'}")
166+
order_book_lines.append(f"{'Bid Orders':<15} | {'Bid Volume':<15} | {'Price':<10} | {'Ask Volume':<15} | {'Ask Orders'}")
156167

157168
for price in all_prices:
158-
bid_vol = bid_counts.get(price, " " * 15)
159-
ask_vol = ask_counts.get(price, " " * 10)
169+
bid_vol = bid_counts.get(price, [" " * 15] * 2)
170+
ask_vol = ask_counts.get(price, [" " * 10] * 2)
160171
formatted_price = f"{price:.2f}"
161-
order_book_lines.append(f"{str(bid_vol):<15} | {str(formatted_price):<10} | {str(ask_vol)}")
172+
order_book_lines.append(f"{str(bid_vol[0])} | {str(bid_vol[1]):<15} | {str(formatted_price):<10} | {str(ask_vol[1]):<15} | {str(ask_vol)[0]}")
162173

163174
order_book_lines.append("```")
164175

@@ -200,9 +211,9 @@ async def view_market(self, ctx: Context, *, market: clean_content):
200211
await ctx.reply(market_str, ephemeral=True)
201212

202213
@commands.hybrid_command(help=LONG_HELP_TEXT, brief=SHORT_HELP_TEXT)
203-
async def bid_market(self, ctx: Context, price: float, *, market: clean_content):
214+
async def bid_market(self, ctx: Context, price: float, qty: int, *, market: clean_content):
204215
"""You would place a bid by using this command
205-
'!bid_market 100 "AAPL"'
216+
'!bid_market 123.4 15 "AAPL"'
206217
"""
207218
if market not in self.live_markets:
208219
await ctx.reply("Market does not exist", ephemeral=True)
@@ -214,15 +225,19 @@ async def bid_market(self, ctx: Context, price: float, *, market: clean_content)
214225
await ctx.reply("Market is closed", ephemeral=True)
215226
return
216227

217-
did_trade = market_obj.bid(price, ctx.author.id)
228+
did_trade = market_obj.bid(price, ctx.author.id, qty)
218229

219230
await ctx.reply("Bid placed", ephemeral=True)
220231

221232
if did_trade is not None:
222233
await ctx.reply(did_trade, ephemeral=False)
223234

224235
@commands.hybrid_command(help=LONG_HELP_TEXT, brief=SHORT_HELP_TEXT)
225-
async def ask_market(self, ctx: Context, price: float, *, market: clean_content):
236+
async def ask_market(self, ctx: Context, price: float, qty: int, *, market: clean_content):
237+
"""You would place a bid by using this command
238+
'!ask_market 123.4 15 "AAPL"'
239+
"""
240+
226241
if market not in self.live_markets:
227242
await ctx.reply("Market does not exist", ephemeral=True)
228243
return
@@ -234,7 +249,7 @@ async def ask_market(self, ctx: Context, price: float, *, market: clean_content)
234249
return
235250

236251

237-
did_trade = market_obj.ask(price, ctx.author.id)
252+
did_trade = market_obj.ask(price, ctx.author.id, qty)
238253

239254
await ctx.reply("Ask placed", ephemeral=True)
240255

@@ -250,14 +265,14 @@ async def positions_market(self, ctx: Context, *, market: clean_content):
250265
market_obj = self.live_markets[market]
251266

252267
user_trades = market_obj.trade_history.get(ctx.author.id, [])
253-
user_asks = [trade.price for trade in user_trades if trade.order_type == 'ask']
254-
user_bids = [trade.price for trade in user_trades if trade.order_type == 'bid']
268+
user_asks = "\n".join(f"{trade.qty}@{trade.price}" for trade in user_trades if trade.order_type == 'ask')
269+
user_bids = "\n".join(f"{trade.qty}@{trade.price}" for trade in user_trades if trade.order_type == 'bid')
270+
net = sum(trade.qty if trade.order_type == 'bid' else -trade.qty for trade in user_trades)
255271

256272
positions = f"Positions for <@{ctx.author.id}> in {market_obj.stock_name}\n"
257-
positions += "Bids\n"
258-
positions += "\n".join([str(bid) for bid in user_bids])
259-
positions += "\n\nAsks\n"
260-
positions += "\n".join([str(ask) for ask in user_asks])
273+
positions += f"Net position: {net}\n"
274+
positions += f"Bids\n{user_bids}"
275+
positions += f"Asks\n{user_asks}"
261276

262277
await ctx.reply(str(positions), ephemeral=True)
263278

0 commit comments

Comments
 (0)