second version

This commit is contained in:
2026-06-07 21:51:20 +00:00
parent 8e2d7efc32
commit 282c841e11
23 changed files with 869 additions and 151 deletions
+46 -7
View File
@@ -11,6 +11,7 @@ import (
"overnight-trading-bot/internal/domain"
"overnight-trading-bot/internal/repository"
"overnight-trading-bot/internal/risk"
)
var ErrBrokerOrdersDisabled = errors.New("broker orders are disabled for current mode")
@@ -113,6 +114,9 @@ func (e *Engine) PlaceLimit(ctx context.Context, order domain.Order) (domain.Ord
return existing, nil
}
}
if e.mode == domain.ModePaper {
return e.placePaperLimit(ctx, order)
}
if !e.mode.AllowsBrokerOrders() {
order.Status = domain.OrderStatusNew
if e.store != nil {
@@ -159,6 +163,28 @@ func (e *Engine) PlaceLimit(ctx context.Context, order domain.Order) (domain.Ord
return posted, nil
}
func (e *Engine) placePaperLimit(ctx context.Context, order domain.Order) (domain.Order, error) {
now := time.Now().UTC()
order.BrokerOrderID = "paper-" + order.ClientOrderID
order.FilledLots = order.QuantityLots
order.AvgFillPrice = order.LimitPrice
order.Status = domain.OrderStatusFilled
order.RawStateJSON = `{"paper_fill":true}`
order.CreatedAt = now
order.UpdatedAt = now
if e.store != nil {
if err := e.store.RunInTx(ctx, func(ctx context.Context, repo repository.Repository) error {
if err := repo.UpsertOrder(ctx, order); err != nil {
return fmt.Errorf("persist paper order: %w", err)
}
return repo.IncrementFreeOrders(ctx, order.TradeDate, order.InstrumentUID, 1)
}); err != nil {
return domain.Order{}, err
}
}
return order, nil
}
func (e *Engine) findExisting(ctx context.Context, order domain.Order) (domain.Order, error) {
orders, err := e.store.ListOrders(ctx, order.AccountIDHash, order.TradeDate, order.TradeDate)
if err != nil {
@@ -286,6 +312,9 @@ func (e *Engine) MonitorUntil(ctx context.Context, order domain.Order, cfg Monit
}
func (e *Engine) repost(ctx context.Context, order domain.Order, cfg MonitorConfig, remaining int64) (domain.Order, error) {
if err := e.ensureRepostBudget(ctx, order, cfg.Instrument); err != nil {
return domain.Order{}, err
}
if err := e.Cancel(ctx, order); err != nil {
return domain.Order{}, err
}
@@ -308,18 +337,28 @@ func (e *Engine) repost(ctx context.Context, order domain.Order, cfg MonitorConf
}
}
func (e *Engine) ensureRepostBudget(ctx context.Context, order domain.Order, instrument domain.Instrument) error {
if e.store == nil || instrument.FreeOrderLimitPerDay <= 0 {
return nil
}
sent, err := e.store.GetFreeOrdersSent(ctx, order.TradeDate, instrument.InstrumentUID)
if err != nil {
return err
}
if instrument.FreeOrderLimitPerDay-sent < 1 {
return fmt.Errorf("%w: %s remaining=0", risk.ErrFreeOrderBudget, instrument.InstrumentUID)
}
return nil
}
func (e *Engine) checkQuoteFresh(book domain.OrderBook) error {
if e.maxQuoteAge <= 0 {
return nil
}
receivedAt := book.ReceivedAt
if receivedAt.IsZero() {
receivedAt = book.Time
if book.ReceivedAt.IsZero() {
return fmt.Errorf("quote received timestamp is missing")
}
if receivedAt.IsZero() {
return fmt.Errorf("quote timestamp is missing")
}
age := time.Since(receivedAt)
age := time.Since(book.ReceivedAt)
if age > e.maxQuoteAge {
return fmt.Errorf("quote age %s exceeds %s", age, e.maxQuoteAge)
}