# Paper Trading Engine Architecture and Simulation Logic in FinceptTerminal

> Explore the FinceptTerminal paper trading engine architecture and simulation logic. Discover its thread-safe C++ simulation layer, atomic SQLite transactions, and realistic margin calculations for accurate backtesting without n...

- Repository: [Fincept Corporation/FinceptTerminal](https://github.com/Fincept-Corporation/FinceptTerminal)
- Tags: architecture
- Published: 2026-04-20

---

**The FinceptTerminal paper trading engine is a self‑contained C++ simulation layer that mimics live broker behavior through atomic SQLite transactions, thread‑safe fill processing, and realistic margin and fee calculations without network dependencies.**

The FinceptTerminal project provides a deterministic backtesting environment through its paper trading subsystem, which replicates real market mechanics for testing strategies and UI demos. This article examines the engine’s three‑tier architecture—spanning the public API, core simulation logic, and SQLite persistence—to explain how it handles order validation, position management, and P&L accounting.

## Core Architecture: API, Engine, and Persistence Layers

The engine follows a strict separation of concerns implemented across three distinct layers in the FinceptTerminal source tree.

The **API layer** exposes C++ functions such as `pt_create_portfolio`, `pt_place_order`, and `pt_fill_order` via [`fincept-qt/src/trading/PaperTrading.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/trading/PaperTrading.h). These functions provide the public interface used by UI components and algorithmic testers.

The **Engine layer** in [`fincept-qt/src/trading/PaperTrading.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/trading/PaperTrading.cpp) implements validation routines, margin calculations, order matching, and fill processing. This layer contains the simulation logic that determines how orders interact with existing positions and portfolio balances.

The **Persistence layer** residing in [`fincept-qt/src/storage/repositories/PaperTradingRepository.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/storage/repositories/PaperTradingRepository.cpp) manages all SQLite interactions. It stores portfolios, orders, positions, and trades, ensuring that every engine action runs inside a database transaction for atomicity.

## Simulation Logic and Order Processing Flow

The simulation follows a deterministic pipeline from portfolio creation through order execution to fill processing.

### Portfolio Creation and Validation

The `pt_create_portfolio` function validates that portfolio names are non‑empty and that initial balance, leverage, and fee rates are positive values. Upon validation, it inserts a new row into the `pt_portfolios` table via the repository layer.

### Order Placement and Margin Checks

When `pt_place_order` is invoked, the engine first validates side, type, quantity, and price parameters. It then performs **margin checks** only on the **net new exposure**—specifically ignoring quantity that would close an existing opposite position. The required margin calculates as:

```cpp
required = net_new_qty * ref_price / portfolio.leverage

```

Here, `ref_price` represents the order price or stop price. Valid orders are inserted into `pt_orders` with a pending status, awaiting simulation fills.

### The Fill Process: Closing, Averaging, and New Positions

The `pt_fill_order` function represents the heart of the simulation logic. It acquires a **global `s_fill_mutex`** to serialize fills per portfolio, preventing race conditions during balance and position updates. The function implements three distinct position handling strategies:

1. **Closing Logic**: When an opposite‑side position exists, the engine calculates realized P&L, updates or removes the existing position, and opens a new opposite‑side position if residual quantity remains after closure.
2. **Averaging Logic**: For same‑side positions, the entry price updates to a weighted average of the existing and newly filled quantities.
3. **New Position Logic**: If no position exists for the symbol and side, the engine inserts a fresh row into `pt_positions`.

Fees calculate as `qty * fill_price * portfolio.fee_rate` but are **deducted only on closing fills**, matching standard exchange behavior where opening trades incur no costs. The portfolio balance updates reflect `balance_change = pnl - fee` exclusively during position reduction or reversal.

After committing the SQLite transaction, the engine emits `paper_trading.order_filled` via the `EventBus` to notify UI components.

## Fee Calculation and P&L Accounting

The simulation uses a realistic fee model defined in the portfolio’s `fee_rate`. During the fill process, the engine distinguishes between opening and closing volume. **Closing fills** trigger fee deduction and realized P&L calculation, while opening fills update position size without balance reduction. This distinction ensures that strategy backtests accurately reflect the cost structure of live trading where fees typically apply to liquidity‑taking trades that reduce exposure.

## Thread Safety and Transactional Integrity

All engine operations run inside explicit SQLite transactions managed through [`fincept-qt/src/storage/sqlite/Database.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/storage/sqlite/Database.cpp). The pattern uses `db.begin_transaction()` followed by `commit()` or `rollback()`, ensuring that a fill is either completely applied—including balance updates, position changes, and trade records—or not applied at all.

The global `QMutex s_fill_mutex` provides thread‑safe access to the fill process. By serializing fills per portfolio, the engine prevents concurrent modifications to balances during high‑frequency simulation scenarios or rapid UI interactions.

## Persistence Layer and Repository Pattern

The `PaperTradingRepository` class provides thin wrappers around SQLite statements. It uses **row mappers** to convert `QSqlQuery` results into domain structs (`PtPortfolio`, `PtOrder`, `PtPosition`, `PtTrade`). CRUD helpers such as `insert_portfolio`, `update_balance`, and `insert_trade` return `Result<T>` types indicating success or specific error strings.

The repository enforces referential integrity through foreign‑key constraints with cascade deletes, while explicit cleanup in `delete_portfolio` guarantees deterministic removal of all related orders, positions, and trades. Statistics aggregation via `get_stats` calculates total P&L, win/loss counts, and largest drawdowns through optimized SQL queries.

## Integration with the Trading Interface

The UI interacts with the paper trading engine through the **UnifiedTrading** façade implemented in [`fincept-qt/src/trading/UnifiedTrading.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/trading/UnifiedTrading.cpp). This routing layer checks the trading mode and directs requests accordingly:

```cpp
if (mode == "paper") {
    auto paper_order = pt_place_order(...);
    pt_fill_order(paper_order.id, fill_price);
}

```

This abstraction allows identical frontend code to operate against either live brokers or the simulation engine without modification. The `EventBus` in [`fincept-qt/src/events/EventBus.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/events/EventBus.h) provides the pub/sub mechanism that broadcasts fill events to portfolio views, order books, and statistics panels.

## Practical Code Examples

### Creating a Paper Portfolio

```cpp
auto portfolio = pt_create_portfolio(
    "My Demo Portfolio",   // name
    1'000'000.0,           // initial balance
    "USD",                 // currency
    1.0,                   // leverage
    "cross",               // margin mode
    0.001,                 // fee rate (0.1%)
    "paper_exchange"       // exchange identifier
);

```

### Placing and Filling Orders

```cpp
// Place a limit order
auto order = pt_place_order(
    portfolio.id,          // portfolio_id
    "AAPL",                // symbol
    "buy",                 // side
    "limit",               // order_type
    100,                   // quantity
    150.0,                 // price
    std::nullopt,          // stop_price
    false                  // reduce_only
);

// Simulate a partial fill
auto trade1 = pt_fill_order(order.id, 149.5, 60);  // 60 shares filled

// Fill remaining quantity
auto trade2 = pt_fill_order(order.id, 149.5, std::nullopt); // fills the rest

```

### Retrieving Statistics

```cpp
PtStats stats = pt_get_stats(portfolio.id);
qDebug() << "Total P&L:" << stats.total_pnl
         << "Win rate:" << stats.win_rate;

```

### Resetting a Portfolio

```cpp
pt_reset_portfolio(portfolio.id);

```

## Summary

- The paper trading engine uses a **three‑layer architecture** separating API declarations in [`PaperTrading.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/PaperTrading.h), simulation logic in [`PaperTrading.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/PaperTrading.cpp), and SQLite persistence in [`PaperTradingRepository.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/PaperTradingRepository.cpp).
- **Margin checks** validate only net new exposure against available buying power, calculated as `net_new_qty * ref / leverage`.
- The **fill process** handles position closing, averaging, and creation atomically inside a global mutex and SQLite transaction to prevent data corruption.
- **Fees are charged only on closing fills**, calculated as `qty * fill_price * fee_rate`, ensuring realistic P&L tracking that matches live exchange behavior.
- **Thread safety** is enforced via `s_fill_mutex`, while **event broadcasting** through `EventBus` keeps UI components synchronized with state changes.

## Frequently Asked Questions

### How does the paper trading engine handle concurrent order fills?

The engine serializes all fill operations per portfolio using a global `QMutex` named `s_fill_mutex`. This prevents race conditions when updating portfolio balances and positions during simultaneous fill requests, ensuring accurate balance accounting even under rapid simulation scenarios.

### Are fees charged on both opening and closing trades?

No. According to the simulation logic in [`PaperTrading.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/PaperTrading.cpp), fees are calculated and deducted **only on closing fills**. The fee formula is `qty * fill_price * portfolio.fee_rate`, and the balance update reflects `pnl - fee` exclusively during position reduction or reversal, matching the behavior of many live exchanges.

### What database does the paper trading engine use for persistence?

The engine uses **SQLite** through the `PaperTradingRepository` class in [`fincept-qt/src/storage/repositories/PaperTradingRepository.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/storage/repositories/PaperTradingRepository.cpp). All operations run inside explicit transactions (`begin_transaction`/`commit`) to guarantee atomicity, ensuring that fills, balance updates, and trade records are persisted together or rolled back on error.

### Can the paper trading engine simulate partial order fills?

Yes. The `pt_fill_order` function accepts an optional fill quantity parameter. If omitted, it fills the remaining order quantity; otherwise, it processes only the specified amount. This allows realistic simulation of partial executions common with limit orders in live markets.