Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
42 changes: 40 additions & 2 deletions internal/market/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Config struct {
MaxQty int // maximum order quantity
MaxOrdersPerSide int // max orders to place per side per tick
AggressiveRate float64 // probability (0-1) of submitting a market order per tick
MaxActiveOrders int // max number of limit orders to keep alive
MaxDeviationPct float64 // cancel orders further than this percentage from the mid price (e.g. 5.0 for 5%)
}

// DefaultConfig returns production defaults matching the competition spec.
Expand All @@ -45,9 +47,17 @@ func DefaultConfig() Config {
MaxQty: 10,
MaxOrdersPerSide: 5,
AggressiveRate: 0.15, // 15% of ticks also submit a market order
MaxActiveOrders: 1000,
MaxDeviationPct: 10.0,
}
}

// TrackedOrder holds the order ID and its assigned price to allow realistic cancellations
type TrackedOrder struct {
ID int
Price float64
}

// Generator produces synthetic market data using GBM and feeds orders
// directly into the matching engine. It runs as a background goroutine.
type Generator struct {
Expand All @@ -61,6 +71,7 @@ type Generator struct {

nextOrderID atomic.Int64
rng *rand.Rand
activeOrders []TrackedOrder // tracks active limit orders and their prices

symbol string // set via SetSymbol for event payloads

Expand All @@ -83,6 +94,7 @@ func New(cfg Config, book *engine.Handle, bookMu *sync.Mutex) *Generator {
stopCh: make(chan struct{}),
// Each symbol's Generator has its own RNG → independent Z shocks vs other books.
rng: rand.New(rand.NewSource(time.Now().UnixNano())),
activeOrders: make([]TrackedOrder, 0, cfg.MaxActiveOrders+20),
}
Comment thread
AdityaRaj212 marked this conversation as resolved.
}

Expand Down Expand Up @@ -252,7 +264,9 @@ func (g *Generator) submitOrders(basePrice float64) {
}

status, _ := g.book.AddLimit(orderID, engine.SideBuy, qty, bidPriceCents)
if status != engine.StatusOK {
if status == engine.StatusOK {
g.activeOrders = append(g.activeOrders, TrackedOrder{ID: orderID, Price: bidPrice})
} else {
log.Printf("[market] bid failed: order=%d price=%d qty=%d err=%s",
orderID, bidPriceCents, qty, status.Error())
}
Expand All @@ -271,12 +285,36 @@ func (g *Generator) submitOrders(basePrice float64) {
}

status, _ := g.book.AddLimit(orderID, engine.SideSell, qty, askPriceCents)
if status != engine.StatusOK {
if status == engine.StatusOK {
g.activeOrders = append(g.activeOrders, TrackedOrder{ID: orderID, Price: askPrice})
} else {
log.Printf("[market] ask failed: order=%d price=%d qty=%d err=%s",
orderID, askPriceCents, qty, status.Error())
}
}

// 1. Price-anchored cancellation (using percentage slabs based on volatility)
var remainingOrders []TrackedOrder
maxDevAbs := basePrice * (g.cfg.MaxDeviationPct / 100.0)
for _, o := range g.activeOrders {
if math.Abs(o.Price-basePrice) > maxDevAbs {
g.book.CancelLimit(o.ID)
} else {
remainingOrders = append(remainingOrders, o)
}
}
g.activeOrders = remainingOrders
Comment thread
AdityaRaj212 marked this conversation as resolved.
Outdated
Comment thread
AdityaRaj212 marked this conversation as resolved.
Outdated
Comment thread
AdityaRaj212 marked this conversation as resolved.
Outdated

// 2. Secondary fallback limit to ensure memory doesn't explode in extreme scenarios
if len(g.activeOrders) > g.cfg.MaxActiveOrders {
excess := len(g.activeOrders) - g.cfg.MaxActiveOrders
for i := 0; i < excess; i++ {
g.book.CancelLimit(g.activeOrders[i].ID)
}
Comment thread
AdityaRaj212 marked this conversation as resolved.
Outdated
// Shift remaining active orders left
g.activeOrders = append(g.activeOrders[:0], g.activeOrders[excess:]...)
}
Comment thread
AdityaRaj212 marked this conversation as resolved.

// Occasionally submit a market order to simulate aggressive traders
// taking liquidity. This creates actual trades in the book.
if g.cfg.AggressiveRate > 0 && g.rng.Float64() < g.cfg.AggressiveRate {
Expand Down
10 changes: 10 additions & 0 deletions internal/market/presets.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,22 @@ func IndianStockPresets() []SymbolPreset {
MaxQty: 200,
MaxOrdersPerSide: 5,
AggressiveRate: 0.08, // ~1 market order/sec — enough for trades, not too many
MaxActiveOrders: 1000,
}

withPrice := func(symbol string, price float64, sigma float64) SymbolPreset {
cfg := base
cfg.InitialPrice = price
cfg.Sigma = sigma

// Set deviation slab based on volatility (sigma)
if sigma <= 0.18 {
cfg.MaxDeviationPct = 5.0
} else if sigma <= 0.20 {
cfg.MaxDeviationPct = 10.0
} else {
cfg.MaxDeviationPct = 20.0
}
return SymbolPreset{
Symbol: symbol,
Class: Stock,
Expand Down
Loading