Skip to content

feat(backtest): implement tick-by-tick fill loop with JSONL output#62

Merged
chizy7 merged 1 commit into
mainfrom
feat/backtest-fill-loop
Apr 19, 2026
Merged

feat(backtest): implement tick-by-tick fill loop with JSONL output#62
chizy7 merged 1 commit into
mainfrom
feat/backtest-fill-loop

Conversation

@chizy7
Copy link
Copy Markdown
Owner

@chizy7 chizy7 commented Apr 19, 2026

Summary

  • Turns BacktestEngine::processMarketData / processStrategyOrders from stubs into a working fill loop: the strategy sees each tick, quotes, and fills are matched against the next tick's bid/ask.
  • Emits JSONL the platform runner already parses: order_filled per fill, strategy_metrics once at the end.
  • Fixes 6 pre-existing BacktestEngineTests failures and one accidental-pass on StopBacktestTest.

Fill mechanics

  • Buy fills if limit >= ask, sell fills if limit <= bid (marketable-limit rule)
  • Slippage pushes fill price against the taker (clamped positive)
  • Fees via tradingFee, cost-basis P&L via weighted-average accounting
  • maxPosition enforced per fill; insufficient-balance buys rejected
  • Portfolio state held under m_stateMutex while mutating

Strategy API (BasicMarketMaker)

  • updateMarketData(MarketDataPoint), synchronous tick feed for backtest
  • getPendingOrders() drain quotes
  • onBacktestFill(side, price, qty, ts) atomic position update (CAS)

JSONL

  • New JsonLogger::log(json) writes raw top-level entries (existing helpers wrap under trading_event, which doesn't match the runner's expected shape)
  • NaN/Inf in summary metrics serialized as JSON null

Summary by CodeRabbit

Release Notes

New Features

  • Added JSON logging method for direct custom log entries
  • Enabled market maker strategy backtesting with market data ingestion and order filling simulation
  • Enhanced backtest engine with improved order execution, cost basis tracking, and final strategy metrics reporting

Bug Fixes

  • Backtest engine now continues replaying data when no strategy is configured instead of returning an error

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b0d172e-4a9f-49c5-85a4-f4f2c0afbd31

📥 Commits

Reviewing files that changed from the base of the PR and between f646057 and 0fe3cd0.

📒 Files selected for processing (8)
  • core/utils/JsonLogger.cpp
  • core/utils/JsonLogger.h
  • main.cpp
  • strategies/backtesting/BacktestEngine.cpp
  • strategies/backtesting/BacktestEngine.h
  • strategies/basic/BasicMarketMaker.cpp
  • strategies/basic/BasicMarketMaker.h
  • tests/unit/BacktestEngineTests.cpp

📝 Walkthrough

Walkthrough

JSON structured logging is integrated into the backtesting framework. JsonLogger gains a direct JSON entry method. BacktestEngine refactors its main loop to process strategy orders synchronously, emit final metrics, and support optional JSON logging. BasicMarketMaker adds backtest-specific methods for market data ingestion, quote generation, and fill reporting.

Changes

Cohort / File(s) Summary
JsonLogger Enhancement
core/utils/JsonLogger.h, core/utils/JsonLogger.cpp
Added public log(const nlohmann::json& entry) overload to write raw JSON entries directly with enabled-check guard pattern.
BacktestEngine Refactoring
strategies/backtesting/BacktestEngine.h, strategies/backtesting/BacktestEngine.cpp
Added setJsonLogger() and emitFinalStrategyMetrics() methods. Refactored main backtest loop: now records market data first, processes pending strategy orders with fill logic (including cost-basis tracking, slippage, and constraints), calls strategy update, emits fills to strategy, and logs metrics. Added m_lastData snapshot and m_avgCostBasis state; introduced applyFillToCostBasis() helper for realized P&L calculation.
BasicMarketMaker Backtest API
strategies/basic/BasicMarketMaker.h, strategies/basic/BasicMarketMaker.cpp
Added public backtest integration methods: updateMarketData() (virtual) for ingesting market data, generateBacktestQuotes() to produce orders with inventory-based skew, getPendingOrders() to atomically retrieve pending orders, and onBacktestFill() to apply fills to position and statistics. Added backtest-specific state (pending orders buffer, bid/ask/mid tracking, mutex-guarded stats).
Application Setup
main.cpp
Conditionally set JSON logger on BacktestEngine after initialization, mirroring existing strategy logger setup.
Test Update
tests/unit/BacktestEngineTests.cpp
Modified StopBacktestTest to configure slower backtest speed (speedMultiplier = 0.01) before engine initialization, adjusting test timing expectations.

Sequence Diagram: BacktestEngine Main Loop with Fill Processing

sequenceDiagram
    participant Engine as BacktestEngine
    participant Strategy as BasicMarketMaker
    participant Logger as JsonLogger
    participant Analyzer as TradeAnalyzer

    loop Per Market Data Point
        Engine->>Engine: Record market data snapshot<br/>(m_lastData)
        
        Engine->>Strategy: getPendingOrders()
        Strategy-->>Engine: pending orders
        
        alt Orders exist
            Engine->>Engine: Validate & fill orders<br/>against m_lastData bid/ask<br/>apply slippage & fees<br/>update cost basis & position
            Engine->>Analyzer: recordTrade()
            Engine->>Strategy: onBacktestFill(side,<br/>price, qty, timestamp)
            Strategy->>Strategy: Update m_position & m_pnl
            
            alt JSON logging enabled
                Engine->>Logger: log(fill entry)
            end
        end
        
        Engine->>Strategy: updateMarketData(dataPoint)
        Strategy->>Strategy: generateBacktestQuotes()
    end
    
    Engine->>Engine: emitFinalStrategyMetrics()
    
    alt JSON logging enabled
        Engine->>Logger: log(strategy_metrics)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A hop through data, quotes align,
Cost basis tallies, fills align,
Orders processed swift and clean,
Backtest metrics JSONL serene—
This market maker now takes the stage! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description clearly explains the objectives, fill mechanics, strategy API changes, and JSONL logging, but lacks the structured template sections (Type of Change, Areas Changed, Testing, Performance Impact, etc.) required by the repository. Fill out the required template sections including Type of Change (New feature), Areas Changed (Core Engine, Trading Strategies), Testing status, and Performance Impact to match repository standards.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: implementing a tick-by-tick fill loop with JSONL output for backtesting.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/backtest-fill-loop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chizy7 chizy7 self-assigned this Apr 19, 2026
@chizy7 chizy7 merged commit 9fb74d6 into main Apr 19, 2026
14 checks passed
github-actions Bot pushed a commit that referenced this pull request Apr 19, 2026
# [1.8.0](v1.7.1...v1.8.0) (2026-04-19)

### Features

* **backtest:** implement tick-by-tick fill loop with JSONL output ([#62](#62)) ([9fb74d6](9fb74d6))
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.8.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant