Phase 1 — Core engine MVP¶
Summary¶
Phase 1 shipped the foundational pipeline: ticks flow through an Engine, a Strategy produces Signals, a MockBroker fills them and prints trades. The full event-driven shape of qkt is established here, including the determinism primitives (Clock, IdGenerator) that every later phase depends on.
What's new¶
Engine— single-process orchestrator that wiresTickFeed → Strategy → BrokertogetherStrategyinterface —onTick(tick, ctx, emit)callback shapeSignalsealed class withBuy/SellvariantsTick— symbol + price + timestamp + optional bid/ask/volumeTickFeedinterface +MockTickFeed— deterministic synthetic random-walk generatorMarketPriceTracker+MarketPriceProvider— producer/consumer-split price storeOrder,OrderTypeenum (MARKET),Trade— execution value typesBrokerinterface +MockBroker— in-process fills at the tracker's latest priceClockinterface +SystemClock/FixedClock— time access goes through this; neverSystem.currentTimeMillis()IdGenerator+SequentialIdGenerator— deterministic order id generation- Sample strategy
EveryNthTickBuyStrategy(buys every Nth tick) - 31 unit tests, ~150-LOC-per-file ceiling enforced
Migration¶
First phase — no migration.
Usage cookbook¶
Run the demo¶
You'll see synthetic ticks print, Strategy emit Buy signals every 5 ticks, MockBroker produce Trades. This is the smallest possible end-to-end loop in qkt.
Write a strategy in Kotlin (pre-DSL)¶
The DSL comes in Phase 5; in Phase 1 strategies are hand-written:
class MyStrategy : Strategy {
private var tickCount = 0
override fun onTick(tick: Tick, ctx: StrategyContext, emit: (Signal) -> Unit) {
if (++tickCount % 10 == 0) {
emit(Signal.Buy(tick.symbol, size = BigDecimal("1.0")))
}
}
}
Inject a deterministic clock for tests¶
val clock = FixedClock(start = 0L)
val engine = Engine(strategy, broker, clock, idGenerator, onTrade = ::println)
engine.onTick(Tick("BTC", BigDecimal("50000"), timestamp = clock.now()))
clock.advance(1_000) // advance 1 second
This is the pattern every test in the codebase uses. FixedClock is what makes backtests reproducible.
Testing patterns¶
- Use anonymous interface impls instead of mocking frameworks:
- JUnit 5 + AssertJ throughout
- Test names are backtick-quoted sentences:
`fills MARKET order at tracker last price`()
Known limitations¶
- No risk validation — strategies can submit any order
- No candles, only ticks (Phase 2b adds candles)
- No event bus, only direct method calls (Phase 2a adds the bus)
- No multi-strategy (Phase 2a adds it)
- No position tracking, P&L, or persistence (Phase 3, 3b)
- No backtest replay engine (Phase 4)
- No DSL — strategies are hand-written Kotlin (Phase 5)
- No concurrency — single-threaded by design