Skip to main content

Command Palette

Search for a command to run...

Optimizing Trading Engine Processes with Sagas and Restate.dev

Published
6 min read
Optimizing Trading Engine Processes with  Sagas and Restate.dev

Introduction

What happens when a trade partially succeeds?

Your order is placed, funds are reserved… but settlement fails. Do you roll back? Retry? Compensate? And how do you prove your system behaves correctly when the market, network, or exchange misbehaves?

This is where Saga workflows, Test-Driven Development (TDD), and Restate.dev intersect.

In this article, we’ll explore how to design a resilient, deterministic trading engine using Saga-based workflows, built and verified through TDD, and powered by Restate.dev. We’ll wrap up with a hands-on tutorial using the Restate SDK and a TypeScript Elysia backend.

Along the way, we’ll ask some uncomfortable (but important) questions:

  • Can your trading engine survive retries without duplicating trades?

  • Can you replay yesterday’s outage and get the same result today?

  • Can you test failure paths as confidently as success paths?

Prerequisites for the hands-on section:

  • Bun installed (install here)

  • Restate.dev installed locally or a cloud account


Why Trading Engines Need Deterministic Workflows

Trading systems operate in hostile environments:

  • Unreliable exchanges

  • Network partitions

  • Timeouts and partial failures

  • Strict financial correctness requirements

A single bug can mean:

  • Double execution

  • Inconsistent balances

  • Regulatory risk

Brain teaser:

If your trade execution retries after a timeout, how do you know it won’t execute twice?

Traditional microservices rely heavily on:

  • Distributed transactions (hard)

  • Message queues (complex)

  • Manual idempotency (error-prone)

This is where Saga workflows shine.


Saga Workflows in a Nutshell

A Saga breaks a long-running transaction into steps, each with:

  • A forward action

  • A compensating action

Example trading saga:

  1. Validate order

  2. Reserve funds

  3. Place an order on the exchange

  4. Settle trade

If step 3 fails, compensate step 2. If step 4 fails, compensate step 3.

Sounds simple — until retries, crashes, and restarts come into play.


Why Restate.dev?

Restate is a lightweight and durable execution engine that allows you to write distributed workflows as if they were local functions — while guaranteeing:

  • Deterministic execution

  • Automatic retries

  • Exactly-once semantics

  • Built-in state persistence

No external workflow engines. No custom retry logic. No fragile state machines.

Key Benefits for Trading Systems

  • Resilience by default – crashes and restarts don’t break workflows

  • Replayability – failed executions can be replayed deterministically

  • Idempotency built-in – no duplicate trades on retries

  • Testability – workflows behave the same in tests and production

Just saying:

How do you feel if your production trading logic behaved exactly like your unit tests?


Test-Driven Development for Saga Workflows

TDD flips development upside down:

  1. Write the test

  2. Watch it fail

  3. Write minimal code

  4. Refactor safely

For Saga workflows, TDD becomes even more powerful.

Why TDD Matters Here

  • You can test failure paths first

  • You can simulate crashes and retries

  • You can verify compensations explicitly

Brain teaser:

Have you ever written a test that expects a failure and validates the rollback?

Restate makes this possible because workflows are pure, deterministic functions with durable state.


Our Trading Engine Architecture Overview

High-level components:

  • Elysia (TypeScript) – API layer

  • Restate SDK – workflow and saga orchestration

  • Exchange Adapter – external exchange API

  • Ledger / State Store – balances and positions

Elysia handles HTTP. Restate handles correctness.


Hands-On Tutorial: Building a Saga-Based Trading Engine

Step 1: Elysia Project Setup (using the default settings)

bun create elysiajs saga-workflow 

# Install Restate TS-SDK
bun add @restatedev/restate-sdk @restatedev/restate-sdk-clients

Initialize Restate and your Elysia backend.


Step 2: Setup Restate and Define the Trading Saga

Create a restate.ts file and paste the code below, it configures restate and

import { tradeWorkflow } from "./saga.ts";
import { serve } from "@restatedev/restate-sdk"
import * as clients from "@restatedev/restate-sdk-clients";

export const restateClient = clients.connect({ url: "http://localhost:8080" }); // the url of your restate server (either cloud or local URL)

serve({
  services: [tradeWorkflow],
  port: 9080
});

Create a saga.ts file and paste the code below,

import { TerminalError, workflow, WorkflowContext } from "@restatedev/restate-sdk"
const OrderManager = {
    validate: (order) => {
        console.log("Order validation logic", order)
    },
    placeOnExchange: (order) => {
        console.log("Order placement logic", order)
    },
    reserve: (order) => {
        console.log("Order reservation logic", order)

    },
    settle: (order) => {
        console.log("Order settlement logic", order)

    },
    release: (order) => {
        console.log("Order placement logic", order)

    },

}
const tradeWorkflowName = "tradeWorkflow"
export const tradeWorkflow = workflow({
    name: tradeWorkflowName,
    handlers: {
        run: async (ctx: WorkflowContext, order: any) => {
            const compensations = [];
            //Workflow:

            // Validate order

            // Reserve funds

            // Place an order on the exchange

            // Settle trade
            try {
                const orderId = ctx.rand.uuidv4();
                compensations.push(() =>
                    ctx.run("release-order", () => OrderManager.release(orderId)),
                );
                const orderRef = await ctx.run("validate", () =>
                    OrderManager.validate(order),
                );
                compensations.push(() =>
                    ctx.run(`release-order-${orderId}`, () => OrderManager.release(orderId)),
                );
                const reservedOrder = await ctx.run("reserve-fund", () =>
                    OrderManager.reserve(orderRef),
                );
                compensations.push(() =>
                    ctx.run(`release-order-${orderId}`, () => OrderManager.release(orderId)),
                );
                const placedOrder = await ctx.run("place-order", () =>
                    OrderManager.placeOnExchange(reservedOrder),
                );
                compensations.push(() =>
                    ctx.run(`release-order-${orderId}`, () => OrderManager.release(orderId)),
                );
                const settledTrade = await ctx.run("settle-trade", () =>
                    OrderManager.settle(placedOrder),
                );
                return {
                    data: settledTrade,
                    message: "Order placed successfully"
                }

            } catch (error) {
                if (error instanceof TerminalError) {
                    for (const compensation of compensations.reverse()) {
                        await compensation();
                    }
                }

            }

        },
    },
});

Notice in the saga.ts file:

  • Each step is durable

  • Retries won’t duplicate execution

  • Compensation is explicit and testable


Step 3: Expose the Workflow via Elysia in the server.ts file

import { Elysia } from "elysia"
import { restateClient } from "./services/restate.ts";
import { tradeWorkflow } from "./services/saga.ts";

export const app = new Elysia()
  .post("/trade", async ({ body }) => {
    return restateClient.workflowClient<typeof tradeWorkflow>({ name: "tradeWorkflow" }, "req-id").workflowSubmit({
      order: body
    })
  })

Now your trading saga is an HTTP API.

Side note:

When was the last time you tested a crash instead of a happy path?


Step 4: Deterministic Replay

In your restate cloud or local instance, Restate allows replaying failed executions:

  • Same inputs

  • Same decisions

  • Same outcomes

This is golden for:

  • Post-mortems

  • Audits

  • Research simulations


Why should this matter to me

I categorized the importance of reliable workflows based on the role of users

Quant Traders

  • Deterministic execution logic

  • Safer strategy automation

  • Reproducible simulations

Developers

  • Fewer edge-case bugs

  • Easier reasoning about failures

  • Stronger confidence in production

Researchers

  • Replayable experiments

  • Deterministic systems modeling

  • Reliable failure analysis


Final Thoughts

What if failures were no longer scary?

By combining Saga workflows, Test-Driven Development, and Restate.dev, you can build trading engines that are:

  • Resilient

  • Deterministic

  • Testable by design

Instead of asking “What if this fails?”, you start asking:

“How do we prove it recovers correctly?”

That mindset shift is where robust trading systems are born. You can check out the full-code in the GitHub repo link: https://github.com/litmus-zhang/personal-playground/tree/backend/saga-workflow/backend/saga-workflow