33
44from discord .ext import commands
55from discord .ext .commands import Bot , Context , check , clean_content
6+ from collections import defaultdict
67
78from utils .utils import is_compsoc_exec_in_guild
89
1415
1516
1617class 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 \n Asks\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