gcx/backend/services/trading-service/internal/application/service/trade_service.go

153 lines
4.7 KiB
Go

package service
import (
"context"
"fmt"
"time"
"github.com/genex/trading-service/internal/domain/entity"
"github.com/genex/trading-service/internal/domain/event"
"github.com/genex/trading-service/internal/domain/repository"
"github.com/genex/trading-service/internal/domain/vo"
)
// TradeService is the application-level use-case service for trading operations.
// It coordinates between the matching engine, repositories, and event publishing.
type TradeService struct {
orderRepo repository.OrderRepository
tradeRepo repository.TradeRepository
matchingService *MatchingService
publisher event.EventPublisher
}
// NewTradeService creates a new TradeService with all dependencies injected.
func NewTradeService(
orderRepo repository.OrderRepository,
tradeRepo repository.TradeRepository,
matchingService *MatchingService,
publisher event.EventPublisher,
) *TradeService {
if publisher == nil {
publisher = &event.NoopEventPublisher{}
}
return &TradeService{
orderRepo: orderRepo,
tradeRepo: tradeRepo,
matchingService: matchingService,
publisher: publisher,
}
}
// PlaceOrderInput carries the validated input for placing an order.
type PlaceOrderInput struct {
UserID string
CouponID string
Side vo.OrderSide
Type vo.OrderType
Price vo.Price
Quantity vo.Quantity
}
// PlaceOrderOutput is the result of a place order use case.
type PlaceOrderOutput struct {
Order *entity.Order
Trades []*entity.Trade
}
// PlaceOrder handles the full workflow of placing an order:
// 1. Create the domain entity
// 2. Persist the order
// 3. Submit to matching engine
// 4. Persist resulting trades
// 5. Update order status
func (s *TradeService) PlaceOrder(ctx context.Context, input PlaceOrderInput) (*PlaceOrderOutput, error) {
// Generate order ID
orderID := fmt.Sprintf("ord-%d", time.Now().UnixNano())
// Create domain entity with validation
order, err := entity.NewOrder(
orderID, input.UserID, input.CouponID,
input.Side, input.Type, input.Price, input.Quantity,
)
if err != nil {
return nil, fmt.Errorf("invalid order: %w", err)
}
// Persist the new order
if err := s.orderRepo.Save(ctx, order); err != nil {
return nil, fmt.Errorf("failed to save order: %w", err)
}
// Submit to matching engine
matchResult := s.matchingService.PlaceOrder(order)
// Persist resulting trades
for _, trade := range matchResult.Trades {
if err := s.tradeRepo.Save(ctx, trade); err != nil {
return nil, fmt.Errorf("failed to save trade %s: %w", trade.ID, err)
}
}
// Update order status after matching
if err := s.orderRepo.UpdateStatus(ctx, order); err != nil {
return nil, fmt.Errorf("failed to update order status: %w", err)
}
return &PlaceOrderOutput{
Order: matchResult.UpdatedOrder,
Trades: matchResult.Trades,
}, nil
}
// CancelOrder cancels an active order by removing it from the order book.
func (s *TradeService) CancelOrder(ctx context.Context, couponID, orderID string, side vo.OrderSide) error {
success := s.matchingService.CancelOrder(couponID, orderID, side)
if !success {
return fmt.Errorf("order not found in order book: %s", orderID)
}
// Update order status in repository
order, err := s.orderRepo.FindByID(ctx, orderID)
if err == nil && order != nil {
order.Cancel()
_ = s.orderRepo.UpdateStatus(ctx, order)
}
return nil
}
// GetOrdersByUser retrieves all orders for a given user.
func (s *TradeService) GetOrdersByUser(ctx context.Context, userID string) ([]*entity.Order, error) {
return s.orderRepo.FindByUserID(ctx, userID)
}
// GetOrder retrieves a single order by ID.
func (s *TradeService) GetOrder(ctx context.Context, orderID string) (*entity.Order, error) {
return s.orderRepo.FindByID(ctx, orderID)
}
// GetTradesByOrder retrieves all trades associated with an order.
func (s *TradeService) GetTradesByOrder(ctx context.Context, orderID string) ([]*entity.Trade, error) {
return s.tradeRepo.FindByOrderID(ctx, orderID)
}
// GetRecentTrades retrieves the most recent trades.
func (s *TradeService) GetRecentTrades(ctx context.Context, limit int) ([]*entity.Trade, error) {
return s.tradeRepo.FindRecent(ctx, limit)
}
// GetOrderBookSnapshot delegates to the matching service for order book data.
func (s *TradeService) GetOrderBookSnapshot(couponID string, depth int) (bids []entity.PriceLevel, asks []entity.PriceLevel) {
return s.matchingService.GetOrderBookSnapshot(couponID, depth)
}
// GetAllOrderBooks returns all active order books (admin use).
func (s *TradeService) GetAllOrderBooks() map[string]*entity.OrderBook {
return s.matchingService.GetAllOrderBooks()
}
// GetTradeCount returns the total trade count.
func (s *TradeService) GetTradeCount() int64 {
return s.matchingService.GetTradeCount()
}