gcx/backend/services/trading-service/internal/domain/entity/order.go

102 lines
2.7 KiB
Go

package entity
import (
"fmt"
"time"
"github.com/genex/trading-service/internal/domain/vo"
)
// OrderStatus represents the lifecycle status of an order.
type OrderStatus string
const (
OrderPending OrderStatus = "pending"
OrderPartial OrderStatus = "partial"
OrderFilled OrderStatus = "filled"
OrderCancelled OrderStatus = "cancelled"
)
// Order is the core domain entity representing a trading order.
type Order struct {
ID string `json:"id"`
UserID string `json:"userId"`
CouponID string `json:"couponId"`
Side vo.OrderSide `json:"side"`
Type vo.OrderType `json:"type"`
Price vo.Price `json:"price"`
Quantity vo.Quantity `json:"quantity"`
FilledQty vo.Quantity `json:"filledQty"`
RemainingQty vo.Quantity `json:"remainingQty"`
Status OrderStatus `json:"status"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// NewOrder creates a new Order with validated inputs.
func NewOrder(id, userID, couponID string, side vo.OrderSide, orderType vo.OrderType, price vo.Price, quantity vo.Quantity) (*Order, error) {
if id == "" {
return nil, fmt.Errorf("order ID cannot be empty")
}
if userID == "" {
return nil, fmt.Errorf("user ID cannot be empty")
}
if couponID == "" {
return nil, fmt.Errorf("coupon ID cannot be empty")
}
if !side.IsValid() {
return nil, fmt.Errorf("invalid order side: %s", side)
}
if !orderType.IsValid() {
return nil, fmt.Errorf("invalid order type: %s", orderType)
}
if orderType == vo.Limit && !price.IsPositive() {
return nil, fmt.Errorf("limit order must have a positive price")
}
now := time.Now()
return &Order{
ID: id,
UserID: userID,
CouponID: couponID,
Side: side,
Type: orderType,
Price: price,
Quantity: quantity,
FilledQty: vo.ZeroQuantity(),
RemainingQty: quantity,
Status: OrderPending,
CreatedAt: now,
UpdatedAt: now,
}, nil
}
// Fill records a partial or full fill of the given quantity.
func (o *Order) Fill(qty vo.Quantity) {
o.FilledQty = o.FilledQty.Add(qty)
o.RemainingQty = o.RemainingQty.Subtract(qty)
o.UpdatedAt = time.Now()
if o.RemainingQty.IsZero() {
o.Status = OrderFilled
} else {
o.Status = OrderPartial
}
}
// Cancel marks the order as cancelled.
func (o *Order) Cancel() {
o.Status = OrderCancelled
o.UpdatedAt = time.Now()
}
// IsActive returns true if the order can still participate in matching.
func (o *Order) IsActive() bool {
return o.Status == OrderPending || o.Status == OrderPartial
}
// IsFilled returns true if the order is fully filled.
func (o *Order) IsFilled() bool {
return o.Status == OrderFilled
}