Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/matching-engine/internal/actor_registy.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,14 @@ func SubscribeSymbol(symbol string, gatewayId string, stream pb.MatchingEngine_S

<-stream.Context().Done()

actor.mu.Lock()
for i, s := range actor.grpcStreams {
if s == stream {
actor.mu.Lock()
actor.grpcStreams = append(actor.grpcStreams[:i], actor.grpcStreams[i+1:]...)
actor.mu.Unlock()
break
}
}
actor.mu.Unlock()

slog.Info(fmt.Sprintf("Gateway %s unsubscribed to %s", gatewayId, symbol))

Expand Down
224 changes: 215 additions & 9 deletions apps/matching-engine/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,22 @@ func (me *MatchingEngine) buildEvents(
UserId: order.UserID,
Data: tradeData,
})

depth, err := me.getDepthEvent()
if err == nil {
events = append(events, depth)
}
if depth == nil {
fmt.Printf("%s", err.Error())
}

tikcer, _ := me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price, me.Asks.BestPriceLevel.Price)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Potential nil pointer dereference when accessing BestPriceLevel.

If me.Bids.BestPriceLevel or me.Asks.BestPriceLevel is nil (e.g., after a trade exhausts one side of the book), accessing .Price will cause a panic.

🐛 Proposed fix: Add nil checks before accessing BestPriceLevel
-		tikcer, _ := me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price, me.Asks.BestPriceLevel.Price)
+		var bidPrice, askPrice int64
+		if me.Bids.BestPriceLevel != nil {
+			bidPrice = me.Bids.BestPriceLevel.Price
+		}
+		if me.Asks.BestPriceLevel != nil {
+			askPrice = me.Asks.BestPriceLevel.Price
+		}
+		ticker, tickerErr := me.getTickerEvent(trade.Price, bidPrice, askPrice)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tikcer, _ := me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price, me.Asks.BestPriceLevel.Price)
var bidPrice, askPrice int64
if me.Bids.BestPriceLevel != nil {
bidPrice = me.Bids.BestPriceLevel.Price
}
if me.Asks.BestPriceLevel != nil {
askPrice = me.Asks.BestPriceLevel.Price
}
ticker, tickerErr := me.getTickerEvent(trade.Price, bidPrice, askPrice)
🤖 Prompt for AI Agents
In `@apps/matching-engine/internal/engine.go` at line 488, me.getTickerEvent is
being called with me.Bids.BestPriceLevel.Price and me.Asks.BestPriceLevel.Price
which can panic if either BestPriceLevel is nil; before the call, compute safe
bestBidPrice and bestAskPrice by checking me.Bids.BestPriceLevel != nil and
me.Asks.BestPriceLevel != nil and use a sensible default (e.g., 0 or NaN) or an
optional/nullable value when nil, then call me.getTickerEvent(trade.Price,
bestBidPrice, bestAskPrice); ensure the variable name tikcer is preserved or
corrected after you replace the direct field access to avoid nil dereference.

if err != nil {
events = append(events, tikcer)
}
if tikcer == nil {
fmt.Printf("%s", err.Error())
}
Comment on lines +488 to +494
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Inverted condition and potential nil dereference for ticker event.

The logic here is inverted: the ticker is appended when err != nil (line 489-490), but it should be appended when err == nil. Additionally, if tikcer is nil, calling err.Error() on line 493 will panic if err is also nil (which it could be since err is from the previous getDepthEvent call, not from getTickerEvent).

🐛 Proposed fix
-		tikcer, _ := me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price, me.Asks.BestPriceLevel.Price)
-		if err != nil {
-			events = append(events, tikcer)
-		}
-		if tikcer == nil {
-			fmt.Printf("%s", err.Error())
-		}
+		ticker, tickerErr := me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price, me.Asks.BestPriceLevel.Price)
+		if tickerErr == nil && ticker != nil {
+			events = append(events, ticker)
+		} else if tickerErr != nil {
+			fmt.Printf("ticker error: %s", tickerErr.Error())
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tikcer, _ := me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price, me.Asks.BestPriceLevel.Price)
if err != nil {
events = append(events, tikcer)
}
if tikcer == nil {
fmt.Printf("%s", err.Error())
}
ticker, tickerErr := me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price, me.Asks.BestPriceLevel.Price)
if tickerErr == nil && ticker != nil {
events = append(events, ticker)
} else if tickerErr != nil {
fmt.Printf("ticker error: %s", tickerErr.Error())
}
🤖 Prompt for AI Agents
In `@apps/matching-engine/internal/engine.go` around lines 488 - 494, The code
currently appends tikcer to events when err != nil and then calls err.Error()
when tikcer == nil which can cause logic inversion and nil deref; update the
block around me.getTickerEvent(trade.Price, me.Bids.BestPriceLevel.Price,
me.Asks.BestPriceLevel.Price) so that you append the returned tikcer to events
only when err == nil, and when tikcer is nil only print or log err if err is
non-nil (i.e., check err != nil before calling err.Error()); also correct any
misspelling of the variable name (tikcer -> ticker) to avoid confusion and
ensure the error variable used is the one returned by getTickerEvent rather than
a stale err from another call.

}

// ---------- FINAL STATE ----------
Expand Down Expand Up @@ -558,6 +574,14 @@ func (me *MatchingEngine) CancelOrderInternal(
Data: data,
})

depth, err := me.getDepthEvent()
if err == nil {
events = append(events, depth)
}
if depth == nil {
fmt.Printf("%s", err.Error())
}

return &CancelOrderInternalResponse{
ID: order.ClientOrderID,
Status: "ORDER_CANCELLED",
Expand Down Expand Up @@ -658,14 +682,20 @@ func (me *MatchingEngine) reduceOrder(
if newRemaining < 0 {
return nil, fmt.Errorf("invalid reduce")
}
volumeDelta := oldRemaining - newRemaining

oldCancelledQuantity := order.CancelledQuantity
newCancelledQuantity := order.CancelledQuantity + volumeDelta

// apply mutation
order.Quantity = *newQuantity
// order.Quantity = *newQuantity
order.RemainingQuantity = newRemaining
order.CancelledQuantity += oldRemaining - newRemaining

order.CancelledQuantity = newCancelledQuantity
Comment on lines +691 to +695
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Redundant cancelled quantity assignment.

Line 693 (order.CancelledQuantity += oldRemaining - newRemaining) and line 695 (order.CancelledQuantity = newCancelledQuantity) both assign to CancelledQuantity. Line 695 overwrites line 693, making line 693 ineffective. Since newCancelledQuantity = order.CancelledQuantity + volumeDelta (line 688) where volumeDelta = oldRemaining - newRemaining (line 685), the final value is the same, but the code is confusing.

♻️ Proposed fix: Remove redundant assignment
 	// apply mutation
-	// order.Quantity = *newQuantity
 	order.RemainingQuantity = newRemaining
-	order.CancelledQuantity += oldRemaining - newRemaining
-
 	order.CancelledQuantity = newCancelledQuantity
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// order.Quantity = *newQuantity
order.RemainingQuantity = newRemaining
order.CancelledQuantity += oldRemaining - newRemaining
order.CancelledQuantity = newCancelledQuantity
order.RemainingQuantity = newRemaining
order.CancelledQuantity = newCancelledQuantity
🤖 Prompt for AI Agents
In `@apps/matching-engine/internal/engine.go` around lines 691 - 695, The code
updates order.CancelledQuantity twice—first with "order.CancelledQuantity +=
oldRemaining - newRemaining" and then immediately with "order.CancelledQuantity
= newCancelledQuantity" (where newCancelledQuantity was computed as
order.CancelledQuantity + volumeDelta and volumeDelta == oldRemaining -
newRemaining), which is redundant and confusing; remove the incremental
assignment (the "+=" line) and keep the direct assignment to
newCancelledQuantity so only one clear update to order.CancelledQuantity
remains, referencing the existing variables order.CancelledQuantity,
newCancelledQuantity, volumeDelta, oldRemaining, and newRemaining to locate the
logic.


// update price level volume
if order.PriceLevel != nil {
volumeDelta := oldRemaining - newRemaining
order.PriceLevel.TotalVolume -= uint64(volumeDelta)
}

Expand Down Expand Up @@ -696,8 +726,16 @@ func (me *MatchingEngine) reduceOrder(
Data: event,
})

depth, err := me.getDepthEvent()
if err == nil {
events = append(events, depth)
}
if depth == nil {
fmt.Printf("%s", err.Error())
}

} else {
event, err := EncodeOrderReducedEvent(order, oldQuantity, oldRemaining)
event, err := EncodeOrderReducedEvent(order, oldQuantity, oldRemaining, newCancelledQuantity, oldCancelledQuantity)
if err != nil {
return nil, err
}
Expand All @@ -706,6 +744,14 @@ func (me *MatchingEngine) reduceOrder(
UserId: order.UserID,
Data: event,
})

depth, err := me.getDepthEvent()
if err == nil {
events = append(events, depth)
}
if depth == nil {
fmt.Printf("%s", err.Error())
}
}

return events, nil
Expand Down Expand Up @@ -847,17 +893,37 @@ func (a *SymbolActor) Run() {
continue
}

// depthEvent, err := a.getDepthEvent()
// if err != nil {
// fmt.Printf("%s", err.Error())
// }
// events = append(events, depthEvent)

// tickerEvent, err := a.getTickerEvent()
// if err != nil {
// fmt.Printf("%s", err.Error())
// }
// events = append(events, depthEvent)

for _, event := range events {
data, err := proto.Marshal(event)
if err != nil {
m.Err <- err
continue
}

for _, stream := range a.grpcStreams {
a.mu.RLock()
streams := make([]pb.MatchingEngine_SubscribeSymbolServer, len(a.grpcStreams))
copy(streams, a.grpcStreams)
a.mu.RUnlock()
for _, stream := range streams {
stream.Send(event)
}

if event.EventType == pbTypes.EventType_DEPTH || event.EventType == pbTypes.EventType_TICKER {
continue
}

if err := a.wal.WriteEntry(data); err != nil {
m.Err <- err
continue
Expand All @@ -873,49 +939,159 @@ func (a *SymbolActor) Run() {
m.Err <- err
continue
}
m.Reply <- response

for _, event := range events {
data, err := proto.Marshal(event)
if err != nil {
m.Err <- err
continue
}

a.mu.RLock()
streams := make([]pb.MatchingEngine_SubscribeSymbolServer, len(a.grpcStreams))
copy(streams, a.grpcStreams)
a.mu.RUnlock()
for _, stream := range streams {
stream.Send(event)
}

if event.EventType == pbTypes.EventType_DEPTH || event.EventType == pbTypes.EventType_TICKER {
continue
}

if err := a.wal.writeEntry(data); err != nil {
m.Err <- err
continue
}
}

m.Reply <- response

case ModifyOrderMsg:
response, events, err := a.engine.ModifyOrderInternal(m.Symbol, m.OrderID, m.UserID, m.ClientModifyID, m.NewPrice, m.NewQuantity)

if err != nil {
m.Err <- err
continue
}
m.Reply <- response

for _, event := range events {
data, err := proto.Marshal(event)
if err != nil {
m.Err <- err
continue
}

a.mu.RLock()
streams := make([]pb.MatchingEngine_SubscribeSymbolServer, len(a.grpcStreams))
copy(streams, a.grpcStreams)
a.mu.RUnlock()
for _, stream := range streams {
stream.Send(event)
}

if event.EventType == pbTypes.EventType_DEPTH || event.EventType == pbTypes.EventType_TICKER {
continue
}

if err := a.wal.writeEntry(data); err != nil {
m.Err <- err
continue
}

}

m.Reply <- response

default:
panic("unknown actor message")
}

}
}

func (me *MatchingEngine) getDepthEvent() (*pb.EngineEvent, error) {
depthLevel := 100
bids := make([]*pb.PriceLevel, 0, depthLevel)
asks := make([]*pb.PriceLevel, 0, depthLevel)

for i := 1; i < depthLevel; i++ {
temp := me.Bids.BestPriceLevel
if temp == nil {
break
}

level := &pb.PriceLevel{
Price: temp.Price,
OrderCount: int64(temp.OrderCount),
Quantity: int64(temp.TotalVolume),
}
bids = append(bids, level)
temp = temp.NextPrice
if temp.NextPrice == nil {
break
}
}
for i := 1; i < depthLevel; i++ {
temp := me.Asks.BestPriceLevel
if temp == nil {
break
}

level := &pb.PriceLevel{
Price: temp.Price,
OrderCount: int64(temp.OrderCount),
Quantity: int64(temp.TotalVolume),
}
bids = append(bids, level)
temp = temp.NextPrice
if temp.NextPrice == nil {
break
}
}
Comment on lines +1018 to +1051
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Multiple bugs in getDepthEvent: off-by-one, wrong slice, and nil pointer dereference.

  1. Off-by-one: Both loops start at i=1 instead of i=0, skipping the first (best) price level.
  2. Wrong slice: Line 1046 appends to bids instead of asks.
  3. Nil pointer dereference: Lines 1030-1033 and 1047-1050 advance temp = temp.NextPrice then check temp.NextPrice == nil, which will panic if temp becomes nil.
🐛 Proposed fix
 func (me *MatchingEngine) getDepthEvent() (*pb.EngineEvent, error) {
 	depthLevel := 100
 	bids := make([]*pb.PriceLevel, 0, depthLevel)
 	asks := make([]*pb.PriceLevel, 0, depthLevel)
 
-	for i := 1; i < depthLevel; i++ {
-		temp := me.Bids.BestPriceLevel
+	temp := me.Bids.BestPriceLevel
+	for i := 0; i < depthLevel && temp != nil; i++ {
-		if temp == nil {
-			break
-		}
-
 		level := &pb.PriceLevel{
 			Price:      temp.Price,
 			OrderCount: int64(temp.OrderCount),
 			Quantity:   int64(temp.TotalVolume),
 		}
 		bids = append(bids, level)
 		temp = temp.NextPrice
-		if temp.NextPrice == nil {
-			break
-		}
 	}
-	for i := 1; i < depthLevel; i++ {
-		temp := me.Asks.BestPriceLevel
+	temp = me.Asks.BestPriceLevel
+	for i := 0; i < depthLevel && temp != nil; i++ {
-		if temp == nil {
-			break
-		}
-
 		level := &pb.PriceLevel{
 			Price:      temp.Price,
 			OrderCount: int64(temp.OrderCount),
 			Quantity:   int64(temp.TotalVolume),
 		}
-		bids = append(bids, level)
+		asks = append(asks, level)
 		temp = temp.NextPrice
-		if temp.NextPrice == nil {
-			break
-		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i := 1; i < depthLevel; i++ {
temp := me.Bids.BestPriceLevel
if temp == nil {
break
}
level := &pb.PriceLevel{
Price: temp.Price,
OrderCount: int64(temp.OrderCount),
Quantity: int64(temp.TotalVolume),
}
bids = append(bids, level)
temp = temp.NextPrice
if temp.NextPrice == nil {
break
}
}
for i := 1; i < depthLevel; i++ {
temp := me.Asks.BestPriceLevel
if temp == nil {
break
}
level := &pb.PriceLevel{
Price: temp.Price,
OrderCount: int64(temp.OrderCount),
Quantity: int64(temp.TotalVolume),
}
bids = append(bids, level)
temp = temp.NextPrice
if temp.NextPrice == nil {
break
}
}
temp := me.Bids.BestPriceLevel
for i := 0; i < depthLevel && temp != nil; i++ {
level := &pb.PriceLevel{
Price: temp.Price,
OrderCount: int64(temp.OrderCount),
Quantity: int64(temp.TotalVolume),
}
bids = append(bids, level)
temp = temp.NextPrice
}
temp = me.Asks.BestPriceLevel
for i := 0; i < depthLevel && temp != nil; i++ {
level := &pb.PriceLevel{
Price: temp.Price,
OrderCount: int64(temp.OrderCount),
Quantity: int64(temp.TotalVolume),
}
asks = append(asks, level)
temp = temp.NextPrice
}
🤖 Prompt for AI Agents
In `@apps/matching-engine/internal/engine.go` around lines 1018 - 1051, The
getDepthEvent depth loops are wrong: they start at i=1 (skipping the best
level), append asks into the bids slice, and advance temp then access
temp.NextPrice causing nil deref; fix by iterating from i=0 up to depthLevel-1,
use a loop condition that checks temp != nil (e.g., for i:=0; i<depthLevel &&
temp!=nil; i++), append the constructed pb.PriceLevel to bids for the bids loop
and to asks for the asks loop, and advance temp safely by setting temp =
temp.NextPrice only after checking temp is non-nil (and avoid accessing
temp.NextPrice directly without verifying temp != nil).


depth := &pb.DepthEvent{
Symbol: me.Symbol,
Sequence: int64(me.TradeSequence),
Timestamp: timestamppb.Now(),
Bids: bids,
Asks: asks,
}

dataByte, err := proto.Marshal(depth)
if err != nil {
return nil, fmt.Errorf("failed to emit depth event of symbol:%s", me.Symbol)
}

event := &pb.EngineEvent{
EventType: pbTypes.EventType_DEPTH,
Data: dataByte,
}

return event, nil
}

func (me *MatchingEngine) getTickerEvent(lastPrice int64, bidPrice int64, askPrice int64) (*pb.EngineEvent, error) {
ticker := &pb.TickerEvent{
Symbol: me.Symbol,
LastPrice: lastPrice,
BidPrice: bidPrice,
AskPrice: askPrice,
}

byteData, err := proto.Marshal(ticker)
if err != nil {
return nil, fmt.Errorf("failed to emit ticker event of symbol:%s", me.Symbol)
}

event := &pb.EngineEvent{
EventType: pbTypes.EventType_TICKER,
Data: byteData,
}

return event, nil
}

func (a *SymbolActor) ReplyWal(from uint64) error {
logs, err := a.wal.ReadFromToLast(from)
if err != nil {
Expand Down Expand Up @@ -978,6 +1154,11 @@ func (a *SymbolActor) ReplyWal(from uint64) error {
buyOrder := a.engine.AllOrders[event.BuyOrderId]
sellOrder := a.engine.AllOrders[event.SellOrderId]

if buyOrder == nil || sellOrder == nil {
return fmt.Errorf("trade replay: order not found (buy=%s, sell=%s)",
event.BuyOrderId, event.SellOrderId)
}

restingOrder := sellOrder
order := buyOrder

Expand Down Expand Up @@ -1008,7 +1189,7 @@ func (a *SymbolActor) ReplyWal(from uint64) error {
if restingOrder.Side == pbTypes.Side_BUY {
obs = a.engine.Bids
}
level := obs.PriceLevels[order.Price]
level := obs.PriceLevels[restingOrder.Price]

level.Remove(restingOrder)
delete(a.engine.AllOrders, restingOrder.ClientOrderID)
Expand Down Expand Up @@ -1077,8 +1258,9 @@ func (a *SymbolActor) ReplyWal(from uint64) error {
order := a.engine.AllOrders[event.Order.OrderId]
level := order.PriceLevel

order.Quantity = event.NewQuantity
// order.Quantity = event.NewQuantity
order.RemainingQuantity = event.NewRemainingQuantity
order.CancelledQuantity += event.NewCancelledQuantity
Comment on lines +1261 to +1263
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incorrect accumulation of CancelledQuantity during WAL replay.

Using += for CancelledQuantity during replay could cause incorrect values if the WAL is replayed multiple times or if the order already had a cancelled quantity. During replay, you should set the value directly from the event, not accumulate.

🐛 Proposed fix
-			order.CancelledQuantity += event.NewCancelledQuantity
+			order.CancelledQuantity = event.NewCancelledQuantity
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// order.Quantity = event.NewQuantity
order.RemainingQuantity = event.NewRemainingQuantity
order.CancelledQuantity += event.NewCancelledQuantity
// order.Quantity = event.NewQuantity
order.RemainingQuantity = event.NewRemainingQuantity
order.CancelledQuantity = event.NewCancelledQuantity
🤖 Prompt for AI Agents
In `@apps/matching-engine/internal/engine.go` around lines 1261 - 1263, During WAL
replay the code incorrectly accumulates cancelled amounts by using
order.CancelledQuantity += event.NewCancelledQuantity; change this to assign the
replayed value directly (order.CancelledQuantity = event.NewCancelledQuantity)
and ensure RemainingQuantity is likewise set from event.NewRemainingQuantity
(order.RemainingQuantity = event.NewRemainingQuantity) so replay is idempotent;
update the logic in the replay handler where these fields are applied to use
direct assignment instead of incremental updates.


volumeDelta := event.OldRemainingQuantity - event.NewRemainingQuantity
order.PriceLevel.TotalVolume -= uint64(volumeDelta)
Expand All @@ -1097,8 +1279,32 @@ func (a *SymbolActor) ReplyWal(from uint64) error {
}
}

// TODO: review this for partiall filled
case pbTypes.EventType_ORDER_REJECTED:
// TODO: Complete this
var event pb.OrderStatusEvent

if err := proto.Unmarshal(log.GetData(), &event); err != nil {
return err
}

order, exists := a.engine.AllOrders[event.OrderId]
if !exists {
continue
}

level := order.PriceLevel

level.Remove(order)
delete(a.engine.AllOrders, order.ClientOrderID)

obs := a.engine.Asks
if order.Side == pbTypes.Side_BUY {
obs = a.engine.Bids
}

if level.IsEmpty() {
obs.RemovePriceLevel(level)
}
Comment on lines +1290 to +1307
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential nil pointer dereference when replaying ORDER_REJECTED.

If order.PriceLevel is nil (which could happen if the order was never added to the book), calling level.Remove(order) on line 1297 will panic.

🐛 Proposed fix: Add nil check
 			order, exists := a.engine.AllOrders[event.OrderId]
 			if !exists {
 				continue
 			}
 
 			level := order.PriceLevel
+			if level == nil {
+				delete(a.engine.AllOrders, order.ClientOrderID)
+				continue
+			}
 
 			level.Remove(order)
 			delete(a.engine.AllOrders, order.ClientOrderID)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
order, exists := a.engine.AllOrders[event.OrderId]
if !exists {
continue
}
level := order.PriceLevel
level.Remove(order)
delete(a.engine.AllOrders, order.ClientOrderID)
obs := a.engine.Asks
if order.Side == pbTypes.Side_BUY {
obs = a.engine.Bids
}
if level.IsEmpty() {
obs.RemovePriceLevel(level)
}
order, exists := a.engine.AllOrders[event.OrderId]
if !exists {
continue
}
level := order.PriceLevel
if level == nil {
delete(a.engine.AllOrders, order.ClientOrderID)
continue
}
level.Remove(order)
delete(a.engine.AllOrders, order.ClientOrderID)
obs := a.engine.Asks
if order.Side == pbTypes.Side_BUY {
obs = a.engine.Bids
}
if level.IsEmpty() {
obs.RemovePriceLevel(level)
}
🤖 Prompt for AI Agents
In `@apps/matching-engine/internal/engine.go` around lines 1290 - 1307, The code
can panic if order.PriceLevel is nil; before calling level.Remove(order) and
level.IsEmpty() (and before obs.RemovePriceLevel(level)), add a nil check for
order.PriceLevel: retrieve level := order.PriceLevel, and if level != nil then
call level.Remove(order), compute obs (using a.engine.Asks / a.engine.Bids as
currently done) and if level.IsEmpty() call obs.RemovePriceLevel(level); always
still delete the order from a.engine.AllOrders (delete(a.engine.AllOrders,
order.ClientOrderID)) regardless of price level being nil. This touches the
logic around a.engine.AllOrders, order.PriceLevel, level.Remove, level.IsEmpty
and obs.RemovePriceLevel.


case pbTypes.EventType_ORDER_FILLED:
var event pb.OrderReducedEvent
Expand Down
2 changes: 1 addition & 1 deletion apps/matching-engine/internal/kafka.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewKafkaProducerWorker(symbol string, dirPath string, wal *SymbolWAL, batch
return nil, err
}

file, err := os.OpenFile(filepath.Join(dirPath, symbol, "checkpoint.meta"), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
file, err := os.OpenFile(filepath.Join(dirPath, symbol, "checkpoint.meta"), os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion apps/matching-engine/internal/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (s *Server) ModifyOrder(ctx context.Context, req *pb.ModifyOrderRequest) (*
res, err := ModifyOrder(req.Symbol, req.OrderId, req.UserId, req.ClientModifyId, req.NewPrice, req.NewQuantity)

if err != nil {
slog.Error("Failed to cancel order")
slog.Error("Failed to modify order")
return nil, err
}

Expand Down
Loading