Trading · Startup, built from zero

From a trader's strategy to a 5,000-customer platform

A founder could make money trading, but only by running his strategies by hand, around the clock. I joined as the first developer and turned that routine into a product thousands of people use.

Role
First developer, later team lead
Timeline
May 2021 to Oct 2025
Team
Grew from 5 to 20 people
Scope
Strategy, engine, web, app, website
Illustration of MyITS with demo data
5000customers in 1 to 2 years
~50Ktrading bots running 24/7
10M+USDT trading volume
4.5 yrsrunning it in production
The problem

A profitable strategy that could not scale

The business idea was simple: package a trader's strategies so customers can run them on their own exchange accounts. The hard part was everything around it.

  • It needed a person, all dayStrategies only worked when someone watched the market and placed orders manually.
  • Real money, no safety netOne bad order or a runaway strategy could cost a customer real money.
  • Three exchanges, three rulebooksBinance, OKX and Bitget each have different limits, order types and quirks.
  • No product for customersThere was no way for customers to sign up, connect accounts or see results.
What I did

From first conversation to running system

  1. Learned trading from zero

    I had no trading background, so I learned the markets and the founder's strategies well enough to turn them into rules a computer can follow.

  2. Turned strategies into code

    Spot and futures strategies, an indicator library, and a backtesting engine to test ideas on past data before risking money.

  3. Built a 24/7 trading engine

    Thousands of bots running in parallel, each with its own account, budget and strategy.

  4. Added risk controls

    Duplicate-order protection, rate limits, strategy throttling and fund anomaly detection.

  5. Built the customer side

    Web portal, React Native app, referral program and the public website.

  6. Ran it and led the team

    Monitoring, incidents and releases for 4.5 years, then led the dev team as it grew.

See it work

What the engine does all day

Demo dataBots trade, the risk guard blocks unsafe orders
Under the hood

How it works

For technical readers: the moving parts, and the mechanism that makes it reliable.

One gateway, many exchanges

Each exchange gets its own adapter that turns its rules, limits and order formats into one common interface. Strategies never talk to an exchange directly.

Queues, not loops

Bot work runs as jobs on BullMQ over Redis, so thousands of bots share workers and a slow exchange cannot block the rest.

Risk before every order

Each order passes duplicate checks, rate limits and per-bot budget checks first. Abnormal fund movement raises an alert.

Backtest before live

New strategies run against historical data in the backtesting engine before any customer money is involved.

Engineering deep dive

How 50K bots trade without double orders

For engineers: the services, the order path, how work is split across servers, and the checks that keep real money safe.

Stack

Services
Node.jsExpressPM2 clusterPiscina
Queues
BullMQ256 shards
Market data
WebSocketRedis Pub/Sub
State
MongoDB replica setRedis
Ops
PrometheusGrafana
Exchanges
BinanceOKXBitgetBingX

Five services, one job each

  • Socket ServerKeeps live price feeds from 4 exchanges and publishes one price snapshot every 300ms.
  • Logic ServerRuns every bot's strategy and risk checks on each 300ms tick, in worker threads.
  • DistributionThe admission gate: creates the order ID, blocks duplicates, rate-limits per user and exchange.
  • Integration pool3 servers with whitelisted IPs that sign and send orders to the exchange.
  • Process OrderApplies order status updates in order, finalises fills and frees the bot for its next trade.

One order, from price tick to fill

Each step is safe to retry. A repeat hits the same order ID and changes nothing.

  1. Socket ServerThe Socket Server publishes {SYMBOL: last} for each exchange every 300ms.
  2. BullMQA bot job lands on its fixed shard queue. The job ID is botId plus the tick, so the same tick is never queued twice.
  3. Logic ServerThe shard owner freezes a price snapshot and runs strategy plus risk in a Piscina worker pool.
  4. RedisBefore any side effect, it re-checks that it still owns the shard. If not, the decision is dropped.
  5. DistributionCreates the clientOrderId, rejects a second in-flight order for the same bot, and applies the rate limit.
  6. Integration poolUpserts the order by clientOrderId first, then sends the signed request to the exchange.
  7. Process OrderStatus events are queued by order ID. Old or out-of-order updates are dropped. A final status unlocks the bot.

Key decisions

Sharded queues with fenced ownership

Problem
One queue per server meant a dead server froze its bots, and adding servers meant moving bots by hand.
Choice
Bots hash into 256 fixed shards. Servers pick shards with weighted rendezvous hashing and hold each one with a 10-second Redis lock plus a fencing number.
Why
A dead server's shards move within seconds. Two servers can never both trade the same bot, because a stale owner fails the check before it sends anything.

Warm standby for every price feed

Problem
A dropped exchange WebSocket meant stale prices, and bots trading on old prices.
Choice
Each exchange has an active publisher, a warm standby that is connected but silent, and a cold standby. The active one holds a Redis lock and heartbeat.
Why
When the publisher dies, the warm standby takes the lock and keeps publishing with no reconnect gap.

One gate in front of every exchange

Problem
Exchanges ban accounts that send too fast, and a retried request could place a second real order.
Choice
Only Distribution creates order IDs. It keeps an active set of in-flight orders and rate-limits each user and exchange (20 per 10s, 100 per minute).
Why
Every retry carries the same ID, and MongoDB enforces it with a unique index. A repeat can never become a second order.

The ownership check, simplified

shard.worker.jssimplified sketch
async function tick(sid, jobs) {
  const me = `${instId}#${fencing[sid]}`;
  // soft check: don't burn CPU on a shard we may have lost
  if (await redis.get(`logic:shard:owner:${sid}`) !== me) return;

  const prices = snapshot();                       // frozen for this 300ms tick
  const decisions = await pool.run({ jobs, prices }); // strategy + risk, off the event loop

  // hard check: never send if another server took the shard meanwhile
  if (await redis.get(`logic:shard:owner:${sid}`) !== me) return;

  for (const d of decisions) await distribution.post('/orders', d);
}

Scale

~50Ktrading bots
4exchanges behind one gateway
300msdecision tick
256shards across the logic servers
5,000customers

Reliability and operations

  • Two checks per tickA soft check before computing saves CPU. A hard check before sending stops two owners.
  • Back-pressureRising 429s, timeouts or event-loop delay shrink batches and slow the tick automatically.
  • Gentle rebalancingShards move only when load is off by more than 10%, one or two at a time, with a cool-down.
  • MonitoringEvery service exposes metrics to Prometheus, with Grafana dashboards and alerts.
Illustration for MyITS
The hard part

I had to learn a new industry while building in it, in a market that never closes.

Every exchange behaves differently, and the market changes fast. A single gateway that absorbs those differences is what let the strategies stay simple and the platform keep growing.

The result

What changed for the business

  • 0 to 5,000 customers in 1 to 2 years
  • About 50,000 bots running around the clock
  • Tens of millions of USDT traded with automated risk control
  • A manual routine became a product that runs itself
Next project

SNSoft

Read the story →