eighth version
This commit is contained in:
@@ -149,6 +149,9 @@ func (e *Engine) placeLimit(ctx context.Context, order domain.Order, freeOrderLi
|
||||
lock := e.lockFor(order.InstrumentUID)
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if e.mode != domain.ModePaper && !e.mode.AllowsBrokerOrders() {
|
||||
return order, ErrBrokerOrdersDisabled
|
||||
}
|
||||
if e.store != nil {
|
||||
existing, err := e.findExisting(ctx, order)
|
||||
if err != nil {
|
||||
@@ -161,13 +164,6 @@ func (e *Engine) placeLimit(ctx context.Context, order domain.Order, freeOrderLi
|
||||
if e.mode == domain.ModePaper {
|
||||
return e.placePaperLimit(ctx, order, freeOrderLimit)
|
||||
}
|
||||
if !e.mode.AllowsBrokerOrders() {
|
||||
order.Status = domain.OrderStatusNew
|
||||
if e.store != nil {
|
||||
return order, e.store.UpsertOrder(ctx, order)
|
||||
}
|
||||
return order, ErrBrokerOrdersDisabled
|
||||
}
|
||||
if e.gateway == nil {
|
||||
return domain.Order{}, errors.New("gateway is nil")
|
||||
}
|
||||
@@ -569,16 +565,24 @@ func (e *Engine) checkQuoteFresh(book domain.OrderBook) error {
|
||||
if e.maxQuoteAge <= 0 {
|
||||
return nil
|
||||
}
|
||||
if book.ReceivedAt.IsZero() {
|
||||
return fmt.Errorf("quote received timestamp is missing")
|
||||
quoteTs := quoteTimestamp(book)
|
||||
if quoteTs.IsZero() {
|
||||
return fmt.Errorf("quote timestamp is missing")
|
||||
}
|
||||
age := e.nowUTC().Sub(book.ReceivedAt)
|
||||
age := e.nowUTC().Sub(quoteTs)
|
||||
if age > e.maxQuoteAge {
|
||||
return fmt.Errorf("quote age %s exceeds %s", age, e.maxQuoteAge)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quoteTimestamp(book domain.OrderBook) time.Time {
|
||||
if !book.Time.IsZero() {
|
||||
return book.Time.UTC()
|
||||
}
|
||||
return book.ReceivedAt.UTC()
|
||||
}
|
||||
|
||||
func (e *Engine) lockFor(instrumentUID string) *sync.Mutex {
|
||||
value, _ := e.mu.LoadOrStore(instrumentUID, &sync.Mutex{})
|
||||
lock, ok := value.(*sync.Mutex)
|
||||
|
||||
@@ -243,6 +243,52 @@ func TestPlaceEntryRejectsStaleQuote(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaceEntryRejectsStaleExchangeQuoteTime(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Date(2026, 6, 8, 18, 20, 0, 0, time.UTC)
|
||||
engine := NewEngine(domain.ModeSandbox, "account", tinvest.NewFakeGateway(), testutil.NewMemoryRepository())
|
||||
engine.SetClock(&fixedClock{now: now})
|
||||
engine.SetMaxQuoteAge(time.Second)
|
||||
_, err := engine.PlaceEntry(ctx, "hash", domain.Instrument{
|
||||
InstrumentUID: "uid",
|
||||
Lot: 1,
|
||||
MinPriceIncrement: decimal.NewFromInt(1),
|
||||
}, now, 1, domain.OrderBook{
|
||||
InstrumentUID: "uid",
|
||||
Time: now.Add(-2 * time.Second),
|
||||
ReceivedAt: now,
|
||||
Bids: []domain.OrderBookLevel{{Price: decimal.NewFromInt(99), QuantityLots: 10}},
|
||||
Asks: []domain.OrderBookLevel{{Price: decimal.NewFromInt(101), QuantityLots: 10}},
|
||||
}, 1, 1)
|
||||
if err == nil {
|
||||
t.Fatal("expected stale exchange quote timestamp error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiveReadonlyDoesNotPersistLocalOrder(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := testutil.NewMemoryRepository()
|
||||
engine := NewEngine(domain.ModeLiveReadonly, "account", tinvest.NewFakeGateway(), repo)
|
||||
_, err := engine.PlaceLimit(ctx, domain.Order{
|
||||
ClientOrderID: "readonly-order",
|
||||
AccountIDHash: "hash",
|
||||
InstrumentUID: "uid",
|
||||
TradeDate: time.Date(2026, 6, 8, 0, 0, 0, 0, time.UTC),
|
||||
Side: domain.SideBuy,
|
||||
OrderType: domain.OrderTypeLimit,
|
||||
LimitPrice: decimal.NewFromInt(100),
|
||||
QuantityLots: 1,
|
||||
Status: domain.OrderStatusNew,
|
||||
AttemptNo: 1,
|
||||
})
|
||||
if !errors.Is(err, ErrBrokerOrdersDisabled) {
|
||||
t.Fatalf("PlaceLimit err=%v, want ErrBrokerOrdersDisabled", err)
|
||||
}
|
||||
if len(repo.Orders) != 0 {
|
||||
t.Fatalf("readonly mode persisted orders: %+v", repo.Orders)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorUntilRepostsAndExpiresAtDeadline(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := testutil.NewMemoryRepository()
|
||||
|
||||
Reference in New Issue
Block a user