DEV Community

Ed Legaspi
Ed Legaspi

Posted on Originally published at czetsuyatech.com

Building a Trading Engine in Java: Beyond BUY and SELL Signals

I Built a Trading Engine in Java — Here’s What It Took
Generating a BUY or SELL signal is relatively straightforward.

Building a system that can reliably execute that signal is a different engineering problem.

When I started developing NERV Trade, I was primarily interested in building a trading system capable of analyzing market conditions across multiple timeframes.

The initial idea was simple:

Market Data
    |
    v
Technical Analysis
    |
    v
Trading Strategy
    |
    v
BUY / SELL / HOLD
Enter fullscreen mode Exit fullscreen mode

But as development progressed, the questions became increasingly difficult.

What happens when an order is only partially filled?

What if the broker sends the same execution twice?

What if the application crashes after processing an execution but before persisting the result?

What if an order reaches the broker, but the connection fails before the application receives an acknowledgment?

And what happens when the application restarts while orders are still active?

These questions pushed NERV Trade beyond signal generation.

It became an exercise in building a durable, broker-neutral trading engine using Java, Spring Boot, and PostgreSQL.

This article introduces the architecture, the engineering problems we encountered, and the decisions that shaped the system.


1. The Architecture We Ended Up Building

NERV Trade now has several distinct responsibilities.

                     MARKET DATA
                          |
                          v
                Multi-Timeframe Analysis
                  M5 / M15 / H1 / H4
                          |
                          v
                       Analysis
                          |
                          v
                       Strategy
                          |
                          v
                         Risk
                          |
                          v
                     Trade Intent
                          |
                          v
                    Order Lifecycle
                          |
                          v
                      BrokerPort
                          |
                +---------+---------+
                |                   |
                v                   v
           Simulator          Binance Spot
                |                   |
                +---------+---------+
                          |
                          v
                  Execution Reports
                          |
                          v
                 Execution Accounting
                          |
                          v
                    PostgreSQL
                          |
               +----------+----------+
               |          |          |
               v          v          v
            Orders     Positions    Journal
                          |
                          v
                 Recovery / Reconciliation
Enter fullscreen mode Exit fullscreen mode

The important design decision is that market analysis, trading decisions, execution, and persistence are separate responsibilities.

A strategy should not need to understand Binance REST endpoints.

A broker adapter should not calculate technical indicators.

And an execution report should not directly mutate arbitrary trading state without passing through the engine's lifecycle rules.

These boundaries became increasingly important as the system evolved.


2. A Trading Signal Is Not a Trade

One of the earliest architectural realizations was that a trading signal and a trade are fundamentally different concepts.

Consider:

BUY BTCUSDT
Enter fullscreen mode Exit fullscreen mode

This represents a trading decision.

It does not mean an order was submitted.

It does not mean the broker accepted the order.

And it certainly does not mean an execution occurred.

In NERV Trade, the lifecycle is closer to:

Strategy Decision
       |
       v
Trading Signal
       |
       v
Trade Intent
       |
       v
Local Order
       |
       v
Broker Order
       |
       v
Execution / Fill
       |
       v
Exposure and Accounting
       |
       v
Lifecycle Completion
Enter fullscreen mode Exit fullscreen mode

Each stage answers a different question.

Concept Responsibility
Strategy decision What does the strategy want to do?
Trading signal What action is being proposed?
Trade intent Which trading lifecycle owns this decision?
Local order What order did NERV create?
Broker order What order does the broker recognize?
Execution What quantity actually traded?
Exposure What quantity remains owned by the intent?
Lifecycle Is the trading operation finished?

Why introduce so many concepts?

Because once execution becomes asynchronous, a single BUY signal can produce multiple orders and multiple executions.

Without clear ownership, accounting and recovery quickly become difficult.


3. A Position Is Not a Trade Either

Another important distinction emerged when implementing trade lifecycle management.

Suppose two strategies independently generate long exposure in the same instrument.

Strategy A
    |
    v
Intent A
    |
    v
BUY 5

Strategy B
    |
    v
Intent B
    |
    v
BUY 3
Enter fullscreen mode Exit fullscreen mode

The aggregate position is:

LONG 8
Enter fullscreen mode Exit fullscreen mode

But there are still two independent trading lifecycles.

If Strategy A exits, it should not automatically close Strategy B's exposure.

This led to an important design principle:

A trade intent owns its exposure. The aggregate position is a derived view.

That distinction also matters when handling reversals.

For example:

Intent A
    |
    +-- BUY 5
    |
    +-- SELL 3
    |
    +-- Remaining exposure: LONG 2
Enter fullscreen mode Exit fullscreen mode

The lifecycle cannot simply be considered complete because an order reached a terminal state.

In NERV Trade, lifecycle completion requires:

  1. The intent's exposure is flat.
  2. All associated orders are terminal.

Both conditions matter.

An intent with zero exposure but an outstanding order may still produce another execution.


4. Partial Fills Changed Everything

A common simplification in trading examples is to assume that an order executes completely.

BUY 10
    |
    v
FILLED 10
Enter fullscreen mode Exit fullscreen mode

Real execution is more complicated.

An order for 10 units might generate:

Order: BUY 10

Execution 1: BUY 3
Execution 2: BUY 4
Execution 3: BUY 3

Total filled: 10
Enter fullscreen mode Exit fullscreen mode

Now the engine must account for each execution independently.

For every accepted execution, the system needs to update the appropriate lifecycle state.

That can include:

  • Order filled quantity
  • Remaining order quantity
  • Intent-owned exposure
  • Cost basis
  • Realized profit and loss
  • Lifecycle completion
  • Trading journal outcome

And those updates must remain consistent.

This becomes particularly interesting when executions arrive asynchronously or are discovered later through reconciliation.

The engine cannot assume that receiving an order acknowledgment means the order has been completely executed.

Acknowledgment and execution are different events.


5. What Happens When the Same Fill Arrives Twice?

Distributed systems frequently deliver information more than once.

Trading infrastructure is no exception.

An execution might arrive through a live stream.

Later, the application might discover the same execution while reconciling with the broker.

After a restart, the same execution might be encountered again.

Consider:

Execution ID: 98765
Symbol: BTCUSDT
Side: BUY
Quantity: 0.001
Enter fullscreen mode Exit fullscreen mode

The engine processes the execution.

Its exposure increases.

Then the same execution arrives again.

If the engine processes it a second time, its internal position becomes incorrect.

Actual execution:

BUY 0.001 BTC

Incorrect internal accounting:

BUY 0.002 BTC
Enter fullscreen mode Exit fullscreen mode

This is not merely a duplicate notification problem.

It is an accounting correctness problem.

NERV Trade therefore uses durable execution identities to prevent duplicate economic effects.

The execution identity must remain stable across:

Live WebSocket Delivery
          |
          v
Application Reconnect
          |
          v
Broker Reconciliation
          |
          v
Application Restart
Enter fullscreen mode Exit fullscreen mode

A reconnect should not transform an existing execution into a new one.

The broader principle is:

Delivery may happen more than once. Accepted economic effects must not.

This is closely related to idempotency problems found in payment processing, messaging systems, and distributed transaction workflows.


6. The Most Dangerous Order Is the One You're Unsure About

Consider this sequence:

NERV Trade                  Broker
     |                         |
     |------ BUY ORDER ------->|
     |                         |
     |                    Order Accepted
     |                         |
     X                         |
 Connection Lost               |
Enter fullscreen mode Exit fullscreen mode

The broker accepted the order.

But the application never received the acknowledgment.

What should happen next?

A naive implementation might retry the request.

retryOrderSubmission();
Enter fullscreen mode Exit fullscreen mode

That is potentially dangerous.

If the first order succeeded, a second submission could create additional exposure.

The application cannot safely assume:

No response = No order
Enter fullscreen mode Exit fullscreen mode

Instead, the system needs to represent uncertainty explicitly.

NERV Trade uses stable order correlation and conservative recovery rules.

Conceptually:

Submit Order
      |
      v
Response Received?
      |
   +--+--+
   |     |
  YES    NO
   |     |
   v     v
Record  Mark Submission
Result  Uncertain
           |
           v
     Reconcile With Broker
           |
           v
     Establish Actual State
Enter fullscreen mode Exit fullscreen mode

The important rule is:

Do not blindly resubmit an order whose outcome is unknown.

First establish what happened.

This is one of the places where trading architecture starts resembling reliable payment processing.

Both involve external side effects that cannot necessarily be rolled back.


7. Persistence Is More Than Saving Orders

Eventually, NERV Trade needed to survive application restarts.

That meant moving lifecycle state into durable persistence.

But simply storing orders was insufficient.

Consider what happens when an execution is accepted.

The system may need to persist:

Execution Identity
       +
Execution Payload
       +
Order Accounting
       +
Intent Exposure
       +
Cost Basis
       +
Realized P&L
       +
Lifecycle Completion
       +
Journal Outcome
Enter fullscreen mode Exit fullscreen mode

These changes represent one logical economic operation.

If only some of them commit, the database can contain contradictory information.

For example:

Execution recorded: YES

Position updated: NO
Enter fullscreen mode Exit fullscreen mode

Now the engine knows the execution happened but its exposure does not reflect it.

The opposite is equally dangerous.

Execution recorded: NO

Position updated: YES
Enter fullscreen mode Exit fullscreen mode

After a restart, the same execution might be accepted again.

NERV Trade addresses this by committing accepted execution accounting atomically using PostgreSQL transactions.

The persistence layer uses:

  • Spring JDBC
  • PostgreSQL
  • Flyway migrations
  • Database uniqueness constraints
  • Intent-level locking
  • Durable execution identities

The domain model remains independent of database annotations.

This allows the core lifecycle logic to remain separate from its persistence implementation.

The goal is not simply to store state.

It is to ensure that the database never commits half of an accepted execution.


8. Restart Recovery Is an Architectural Feature

Imagine an application running with active orders and nonzero exposure.

Then the process stops.

When it starts again, it must recover more than the latest aggregate position.

It needs to understand:

  • Which trade intents remain active
  • Which orders belong to those intents
  • Which executions have already been accepted
  • Which orders are terminal
  • Which submissions remain uncertain
  • What exposure belongs to each intent
  • Whether additional broker reconciliation is necessary

The engine cannot treat a restart as the beginning of a new trading session with no history.

The broker still has its orders.

The account still has its assets.

And previously accepted executions still matter.

Recovery must therefore reconstruct the engine's durable state and reconcile unresolved external activity.

This is why persistence and recovery became fundamental parts of the architecture rather than optional infrastructure.


9. Why We Built the Simulator First

Before integrating a real exchange, NERV Trade used a deterministic simulator.

Initially, a simulator might seem like a convenient way to test trading strategies.

But it became something more important.

It allowed us to validate the execution engine itself.

For example:

Strategy
    |
    v
Trade Intent
    |
    v
BrokerPort
    |
    v
Deterministic Simulator
    |
    v
ExecutionReportPort
    |
    v
Durable Lifecycle
Enter fullscreen mode Exit fullscreen mode

The simulator exercises the same conceptual execution boundaries that an external broker uses.

That makes it possible to test:

  • Partial fills
  • Multiple executions
  • Duplicate execution delivery
  • Order lifecycle transitions
  • Reversals
  • Intent-level exposure
  • Realized P&L
  • Failure handling
  • Restart recovery

The simulator also provides something a live exchange cannot easily guarantee:

Reproducible execution scenarios.

When a test fails, we can replay the same conditions.

When an invariant is violated, we can isolate the sequence that caused it.

This became especially valuable while hardening the lifecycle.

The simulator was no longer just a trading-strategy testing tool.

It became a controlled environment for validating execution correctness.


10. Then We Connected It to Binance

Once the lifecycle and persistence architecture had matured, we introduced a Binance Spot Testnet adapter.

The important requirement was that Binance should remain an adapter.

It should not become the owner of NERV Trade's business logic.

                  NERV Trade Core
                        |
                        v
                    BrokerPort
                        |
             +----------+----------+
             |                     |
             v                     v
        Simulator             Binance Spot
                                   |
                                   v
                              REST / WS
Enter fullscreen mode Exit fullscreen mode

The adapter handles exchange-specific concerns such as:

  • Authentication and request signing
  • Exchange symbol filters
  • Order submission
  • Broker order correlation
  • Execution event mapping
  • Historical candle retrieval
  • Live market-data streams
  • Reconnection and gap recovery
  • Broker reconciliation

The engine continues to own:

  • Analysis
  • Strategy decisions
  • Risk decisions
  • Trade intents
  • Execution accounting
  • Lifecycle state
  • Persistence
  • Recovery rules

This separation is particularly important because Binance Spot has different trading semantics from a general-purpose simulator.

For example, ordinary Spot trading cannot be treated as if it supports unrestricted short exposure.

The adapter must respect the capabilities of the venue without redefining the engine's core domain model.


11. What Did We Actually Verify?

At the initial Binance Spot Testnet adapter milestone, NERV Trade reached:

601 passing tests, with zero failures, errors, or skips.

The verification included the existing deterministic simulator, lifecycle and persistence tests, along with the new adapter test coverage.

The packaged simulator also continued to pass its deterministic execution scenario.

However, there is an important distinction.

Those 601 passing tests did not mean we had already proven every assumption against the real Binance Testnet.

At that milestone, live exchange verification had not yet been executed.

The adapter was implemented and locally verified.

Actual exchange behavior still needed to be validated separately.

That distinction matters.

A mocked broker can prove that your application handles the behavior you modeled.

It cannot prove that you modeled every relevant behavior of the real broker correctly.


12. What Building NERV Trade Taught Me

The biggest lesson wasn't about finding better trading signals.

It was about separating different kinds of correctness.

Analysis correctness

Does the engine interpret market data consistently and without using information from the future?

Decision correctness

Does the strategy produce decisions based on the intended analysis and risk constraints?

Execution correctness

Does the system accurately represent orders, acknowledgments, fills, and lifecycle transitions?

Accounting correctness

Does every accepted execution produce exactly one set of economic effects?

Recovery correctness

Can the application restart without forgetting what it previously owned or processed?

Integration correctness

Does the broker adapter preserve the semantics of the external venue without contaminating the core architecture?

These are different problems.

And each deserves its own design and verification strategy.

A profitable backtest cannot establish execution correctness.

A successful API request cannot establish accounting correctness.

And hundreds of passing unit tests cannot, by themselves, establish real-exchange correctness.


13. Where the Engineering Journey Goes Next

NERV Trade didn't begin with this architecture.

It evolved as increasingly difficult problems exposed weaknesses in the previous design.

Each one forced another architectural decision:

Deterministic Analysis
          |
          v
Multi-Timeframe Context
          |
          v
Strategy and Risk Boundaries
          |
          v
Trade Intent Ownership
          |
          v
Order and Execution Lifecycle
          |
          v
Execution Idempotency
          |
          v
Atomic Persistence
          |
          v
Restart Recovery
          |
          v
Broker Integration
          |
          v
Reconciliation and Safety
Enter fullscreen mode Exit fullscreen mode

And that is what this series will document.

Not a collection of trading indicators.

Not a promise of profitable strategies.

And not another tutorial that stops after printing a BUY signal.

Instead, we'll explore the engineering decisions involved in building a trading engine that can reason about its own state, execute through different brokers, and recover when things go wrong.

We'll start at the beginning.

With a deceptively simple question:

How difficult can it really be to build a trading bot?


About NERV Trade

NERV Trade is part of NERV — Next-Generation Engineering for Runtime Velocity, an engineering initiative focused on building reusable Java and Spring-based systems.

The project explores trading architecture, deterministic analysis, execution lifecycle management, durable persistence, broker integration, and operational safety.

This article is adapted from the original CzetsuyaTech publication:

I Built a Trading Engine in Java — Here's What It Took

Continue the Series

Next: A Trading Bot Is Easy. A Trading Engine Is Not.

The next article rewinds to the beginning and explores how a simple signal-generation idea evolved into a much larger engineering problem.

This series focuses on software engineering and trading infrastructure, not financial advice or claims of trading profitability.

Top comments (0)