DEV Community

BornToWin
BornToWin

Posted on Originally published at guskarls.substack.com

Building a Robinhood Trading Bot with TypeScript: Market Data, Risk, Execution, and Reconciliation

A practical architecture for turning a trading strategy into a reliable automated trading system.

Building a trading bot is easy to describe:

Market Data
    ↓
Strategy
    ↓
 Order
Enter fullscreen mode Exit fullscreen mode

Building one that can safely run unattended is a different problem.

A serious automated trading system has to handle:

  • market data
  • strategy signals
  • risk limits
  • order state
  • retries
  • idempotency
  • execution
  • position tracking
  • reconciliation
  • monitoring

Robinhood currently provides a Crypto Trading API for programmatic access to market data, account information, and crypto order placement. Robinhood also provides a Trading MCP for its Agentic Trading product, which introduces another way to automate trading workflows. (docs.robinhood.com)

This article focuses on the engineering architecture behind a Robinhood trading bot.


The Architecture

I would structure the system like this:

                  ┌──────────────────┐
                  │   Market Data    │
                  └────────┬─────────┘
                           ↓
                  ┌──────────────────┐
                  │ Strategy Engine  │
                  └────────┬─────────┘
                           ↓
                  ┌──────────────────┐
                  │   Risk Engine    │
                  └────────┬─────────┘
                           ↓
                  ┌──────────────────┐
                  │  Order Manager   │
                  └────────┬─────────┘
                           ↓
                  ┌──────────────────┐
                  │ Execution Engine │
                  └────────┬─────────┘
                           ↓
                     ┌───────────┐
                     │ Robinhood │
                     └─────┬─────┘
                           ↓
                  ┌──────────────────┐
                  │ Position Manager │
                  └────────┬─────────┘
                           ↓
                  ┌──────────────────┐
                  │ Reconciliation   │
                  └──────────────────┘
Enter fullscreen mode Exit fullscreen mode

The frontend should consume this system.

It shouldn't contain the core trading logic.


1. Market Data

The first layer is the market-data service.

A simple model:

type MarketPrice = {
  symbol: string;
  bid?: number;
  ask?: number;
  last?: number;
  timestamp: number;
};
Enter fullscreen mode Exit fullscreen mode

The data service should also track:

source
timestamp
symbol
market
data freshness
Enter fullscreen mode Exit fullscreen mode

A trading strategy shouldn't blindly trust every price it receives.

For example:

function isFresh(
  timestamp: number,
  maxAgeMs: number,
): boolean {
  return Date.now() - timestamp <= maxAgeMs;
}
Enter fullscreen mode Exit fullscreen mode

Then:

if (!isFresh(price.timestamp, 5_000)) {
  throw new Error("Market data is stale");
}
Enter fullscreen mode Exit fullscreen mode

The exact threshold depends on the strategy.

The important principle is:

A trading decision should know the age and quality of its data.


2. Strategy Engine

The strategy should produce a signal.

It should not submit the order.

type TradingSignal = {
  symbol: string;
  side: "BUY" | "SELL";
  quantity: number;
  reason: string;
};
Enter fullscreen mode Exit fullscreen mode

Example:

const signal: TradingSignal = {
  symbol: "BTC-USD",
  side: "BUY",
  quantity: 0.01,
  reason: "Momentum threshold reached",
};
Enter fullscreen mode Exit fullscreen mode

The architecture remains:

Market Data
     ↓
Strategy
     ↓
Signal
Enter fullscreen mode Exit fullscreen mode

This makes strategies replaceable.

You can later implement:

Momentum
Mean Reversion
DCA
Rebalancing
Arbitrage
AI Signals
Enter fullscreen mode Exit fullscreen mode

without changing the execution layer.


3. Risk Engine

The risk engine is the gate between strategy and execution.

Strategy
   ↓
 Risk
   ↓
Approved / Rejected
Enter fullscreen mode Exit fullscreen mode

Example:

type RiskContext = {
  portfolioValue: number;
  currentExposure: number;
  orderValue: number;
  maxOrderValue: number;
  maxExposure: number;
};
Enter fullscreen mode Exit fullscreen mode

Then:

function validateRisk(ctx: RiskContext): void {
  if (ctx.orderValue > ctx.maxOrderValue) {
    throw new Error("Maximum order size exceeded");
  }

  if (
    ctx.currentExposure + ctx.orderValue >
    ctx.maxExposure
  ) {
    throw new Error("Maximum exposure exceeded");
  }
}
Enter fullscreen mode Exit fullscreen mode

A production risk engine can also enforce:

Maximum order size
Maximum position size
Maximum portfolio exposure
Maximum daily loss
Maximum number of open orders
Maximum slippage
Minimum balance
Maximum price age
Enter fullscreen mode Exit fullscreen mode

The strategy decides what it wants.

The risk engine decides whether it is allowed.


4. Order State Machine

This is where many simple trading bots start to break down.

An order shouldn't be modeled as:

status: "OPEN" | "CLOSED"
Enter fullscreen mode Exit fullscreen mode

Instead, use explicit states:

CREATED
   ↓
RISK_CHECKED
   ↓
SUBMITTED
   ↓
PENDING
   ↓
FILLED
Enter fullscreen mode Exit fullscreen mode

With failure paths:

PENDING
   ├──→ FILLED
   ├──→ CANCELLED
   ├──→ REJECTED
   └──→ FAILED
Enter fullscreen mode Exit fullscreen mode

And potentially:

PENDING
   ↓
PARTIALLY_FILLED
   ↓
FILLED
Enter fullscreen mode Exit fullscreen mode

This matters because:

Submitted does not mean filled.

A trading engine must distinguish intent, submission, and actual execution.


5. Intent vs Execution vs Result

Consider:

BUY 0.01 BTC
Enter fullscreen mode Exit fullscreen mode

That's the strategy's intent.

Then:

Order submitted
Enter fullscreen mode Exit fullscreen mode

That's execution.

Then:

0.0098 BTC actually executed
Enter fullscreen mode Exit fullscreen mode

That's the result.

So:

Intent
  ≠
Execution
  ≠
Result
Enter fullscreen mode Exit fullscreen mode

This mental model makes the rest of the system much easier to design.


6. Idempotency

Now consider a network timeout:

Bot
 ↓
Create Order
 ↓
Request sent
 ↓
Timeout
Enter fullscreen mode Exit fullscreen mode

Did Robinhood receive the request?

The application may not know.

If the bot blindly retries, it can accidentally create another order.

Robinhood's Crypto Trading API documents client_order_id as the client-provided order identifier and uses it for idempotency validation. (docs.robinhood.com)

So create a unique identifier:

const clientOrderId = crypto.randomUUID();
Enter fullscreen mode Exit fullscreen mode

Store it before execution.

Then:

Retry
  ↓
Same clientOrderId
  ↓
Same logical order
Enter fullscreen mode Exit fullscreen mode

The important principle is:

Retries must be safe.


7. Execution Service

The execution service translates an approved order into a Robinhood API operation.

Signal
  ↓
Risk
  ↓
Order
  ↓
Execution Service
  ↓
Robinhood
Enter fullscreen mode Exit fullscreen mode

A basic domain model:

type ExecutionResult = {
  orderId: string;
  clientOrderId: string;
  symbol: string;
  requestedQuantity: number;
  executedQuantity: number;
  averagePrice?: number;
  status: "PENDING" | "FILLED" | "FAILED";
};
Enter fullscreen mode Exit fullscreen mode

Keep this layer isolated.

The strategy should not know how the Robinhood request is constructed.


8. Position Management

After execution, the system must update positions.

type Position = {
  symbol: string;
  quantity: number;
  averageEntryPrice: number;
  realizedPnl: number;
  unrealizedPnl: number;
};
Enter fullscreen mode Exit fullscreen mode

Then calculate:

Position
   ↓
Current Price
   ↓
Unrealized PnL
Enter fullscreen mode Exit fullscreen mode

and:

Closed Trades
   ↓
Realized PnL
Enter fullscreen mode Exit fullscreen mode

This data is also useful for:

  • portfolio dashboards
  • performance reports
  • risk calculations
  • alerts
  • strategy evaluation

9. Reconciliation

Real-time updates are not enough.

Systems fail.

You can have:

API timeout
Network failure
Worker crash
Missed update
Duplicate message
Database outage
Enter fullscreen mode Exit fullscreen mode

So the bot needs reconciliation.

Fast path

Order Update
     ↓
Update Internal State
Enter fullscreen mode Exit fullscreen mode

Safety path

Periodic Reconciliation
     ↓
Read Current Account State
     ↓
  Compare
     ↓
  Repair
Enter fullscreen mode Exit fullscreen mode

For example:

Every 30–60 seconds

Check balances
Check positions
Check open orders
Check recent executions
Compare internal state
Enter fullscreen mode Exit fullscreen mode

This creates a useful rule:

Events provide speed. Reconciliation provides confidence.


10. Database Design

A basic relational model could contain:

users
accounts
strategies
orders
executions
positions
reconciliation_runs
Enter fullscreen mode Exit fullscreen mode

For example:

CREATE TABLE orders (
    id UUID PRIMARY KEY,
    client_order_id TEXT UNIQUE NOT NULL,
    symbol TEXT NOT NULL,
    side TEXT NOT NULL,
    quantity NUMERIC NOT NULL,
    status TEXT NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

The unique client_order_id protects against duplicate logical orders.


11. Worker Architecture

I wouldn't put everything inside the HTTP request.

Instead:

API
 ↓
Create Strategy Job
 ↓
Queue
 ↓
Trading Worker
 ↓
Risk
 ↓
Execution
Enter fullscreen mode Exit fullscreen mode

A worker can be responsible for:

strategy evaluation
order submission
order tracking
reconciliation
Enter fullscreen mode Exit fullscreen mode

This also makes retries easier to control.


12. Trading Bot Loop

A simple bot loop might be:

async function tradingCycle() {
  const market = await marketData.get("BTC-USD");

  if (!isFresh(market.timestamp, 5_000)) {
    return;
  }

  const signal = strategy.evaluate(market);

  if (!signal) {
    return;
  }

  risk.validate(signal);

  const order = await orderManager.create(signal);

  await execution.submit(order);
}
Enter fullscreen mode Exit fullscreen mode

The important part isn't the loop itself.

The important part is the boundaries around it.


13. Why Risk Must Be Independent

Imagine a strategy bug:

BUY
BUY
BUY
BUY
BUY
Enter fullscreen mode Exit fullscreen mode

Without an independent risk layer:

Strategy
    ↓
Execution
    ↓
Large position
Enter fullscreen mode Exit fullscreen mode

With risk:

Strategy
    ↓
Risk Engine
    ↓
Maximum exposure reached
    ↓
 REJECT
Enter fullscreen mode Exit fullscreen mode

This is why I consider the risk layer a first-class component rather than an optional feature.


14. Adding AI

Robinhood's Agentic Trading introduces a new possibility: an AI agent can interact with supported trading functionality through Robinhood's MCP. (robinhood.com)

I would integrate it like this:

AI Agent
    ↓
Trade Intent
    ↓
 Policy
    ↓
Risk Engine
    ↓
Execution
    ↓
Robinhood
Enter fullscreen mode Exit fullscreen mode

Not:

AI
 ↓
Direct Trade
Enter fullscreen mode Exit fullscreen mode

For example:

AI:
"Buy $10,000 BTC"

Policy:
Maximum automated order = $2,000

Risk:
REJECT
Enter fullscreen mode Exit fullscreen mode

The AI can generate the intent.

The deterministic system remains responsible for authorization.


15. Stock Tokens and Robinhood Chain

There is a related onchain opportunity.

Robinhood Chain is a separate EVM-compatible Layer 2, and its Stock Tokens are ERC-20 assets with Chainlink price feeds. Robinhood documents trading, lending, and other applications that can be built around Stock Tokens. (docs.robinhood.com)

The architecture could be:

Stock Token
     ↓
Price Oracle
     ↓
 Strategy
     ↓
    Risk
     ↓
Onchain Execution
     ↓
  Position
Enter fullscreen mode Exit fullscreen mode

For example, a client could want an automated rebalancing application:

Target Allocation
       ↓
Current Portfolio
       ↓
  Difference
       ↓
Trade Signal
       ↓
     Risk
       ↓
Onchain Execution
Enter fullscreen mode Exit fullscreen mode

This is where automated trading and Stock Token applications overlap.


16. Example Technology Stack

A practical stack could be:

Backend
---------
TypeScript
Node.js
viem
PostgreSQL
Redis

Frontend
---------
Next.js
React
TypeScript

Robinhood
---------
Crypto Trading API
Trading MCP

Onchain
-------
Solidity
Foundry
Robinhood Chain
Chainlink
Enter fullscreen mode Exit fullscreen mode

Robinhood Chain is EVM-compatible and supports familiar Ethereum tooling. (docs.robinhood.com)


17. Production Checklist

Before calling a trading bot production-ready, I'd want:

✓ Market-data validation
✓ Risk limits
✓ Order state machine
✓ Idempotency
✓ Retry handling
✓ Position tracking
✓ Reconciliation
✓ Secure credentials
✓ Monitoring
✓ Alerting
✓ Audit logs
✓ Failure recovery
Enter fullscreen mode Exit fullscreen mode

The happy path is the easy part.

The difficult engineering is what happens when something goes wrong.


Final Architecture

A practical Robinhood trading system becomes:

                    MARKET DATA
                         │
                         ▼
                     STRATEGY
                         │
                         ▼
                       RISK
                         │
                         ▼
                       ORDER
                         │
                         ▼
                     EXECUTION
                         │
                         ▼
                    ROBINHOOD
                         │
                         ▼
                    POSITIONS
                         │
                         ▼
                 RECONCILIATION
Enter fullscreen mode Exit fullscreen mode

And with AI:

                  AI AGENT
                     ↓
                TRADE INTENT
                     ↓
                   POLICY
                     ↓
                    RISK
                     ↓
                EXECUTION
                     ↓
                 ROBINHOOD
Enter fullscreen mode Exit fullscreen mode

For Robinhood Chain Stock Token applications:

                STOCK TOKENS
                     ↓
                  ORACLE
                     ↓
                 STRATEGY
                     ↓
                    RISK
                     ↓
             ONCHAIN EXECUTION
                     ↓
                  POSITION
Enter fullscreen mode Exit fullscreen mode

The key lesson is simple:

A trading bot isn't an API call wrapped in a loop.

It is a stateful financial system.

The strategy is only one component.

The real engineering value is in making the entire pipeline—data → decision → risk → execution → state → reconciliation—reliable.

Top comments (0)