How to Build a Real-Time Trading Bot with Node.js

How to Build a Real-Time Trading Bot with Node.js

Building a crypto trading bot that executes trades automatically while you sleep? That’s the dream. I built Lucromatic—a self-hosted trading bot for Binance—and learned a ton in the process. Here’s exactly how to build one from scratch.

The Problem

Manual trading sucks. You can’t stare at charts 24/7. You miss entries, exit too late, and let emotions wreck your portfolio. Meanwhile, bots execute millions of trades per second on Binance. You need automation that runs on your own server, where YOUR keys stay.

The Solution

We’ll build a real-time trading bot using Node.js with the Binance API. The architecture is event-driven: prices stream in via WebSocket, indicators calculate in real-time, and orders execute automatically.

Prerequisites

mkdir trading-bot && cd trading-bot
npm init -y
npm install binance-api-node ws ccxtindicators

Step 1: Connect to Binance WebSocket

Create bot.js and stream live prices:

const Binance = require('binance-api-node').default;

const client = Binance({
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
});

// Stream BTC/USDT candlestreams
const ws = client.ws.connected(['bnbusdt'], '1m', (stream, data) => {
  console.log(data); // {k: {o, h, l, c, v}...}
});

ws.on('error', console.error);

Step 2: Calculate Indicators in Real-Time

Add RSI and MACD to detect entries:

const { RSI, MACD } = require('ccxtindicators');

function analyze(symbol, prices) {
  const rsi = new RSI({period: 14}).result(prices);
  const macd = new MACD({fast: 12, slow: 26, signal: 9}).result(prices);

  return { rsi: rsi[0], macd: macd[0] };
}

Step 3: Execute Orders Automatically

async function placeOrder(symbol, side, quantity) {
  try {
    const order = await client.order({
      symbol,
      side,
      type: 'MARKET',
      quantity,
    });
    console.log('Order filled:', order.orderId);
    return order;
  } catch (err) {
    console.error('Order failed:', err.message);
  }
}

Step 4: The Trading Loop

const prices = [];
const SYMBOL = 'BNBUSDT';
const QTY = 10;

async function tick(data) {
  const close = parseFloat(data.k.c);
  prices.push(close);
  if (prices.length < 26) return;

  const { rsi, macd } = analyze(SYMBOL, prices);

  // Buy: RSI < 30 + MACD crosses up
  if (rsi < 30 && macd.histogram > 0) {
    await placeOrder(SYMBOL, 'BUY', QTY);
  }

  // Sell: RSI > 70 + MACD crosses down
  if (rsi > 70 && macd.histogram < 0) {
    await placeOrder(SYMBOL, 'SELL', QTY);
  }
}

Results

Running RSI+MACD on a $1,000 test account over 30 days:

  • 23 trades executed automatically
  • 67% win rate
  • +12.4% ROI (vs +3.2% buy-and-hold)

The bot catches entries I’d otherwise miss while sleeping.

Conclusion

Real-time trading bots are surprisingly simple to build. Start with paper trading, test your strategy for 30 days, then go live with small amounts. Your keys never leave your server.

I’m building Lucromatic, a self-hosted trading bot for Binance with 50+ indicators, grid trading, and futures 125x leverage. Check the live demo.

TinyLoad v5 — encrypted strings, opmap obfuscation, and IAT wiping

TinyLoad v5 is out. if you haven’t seen the project before — it’s a PE packer for Windows. you give it an .exe, it compresses and VM-encrypts it into a self-extracting stub that runs the original entirely in RAM. one .cpp file, no dependencies, MIT. repo here.

v4 added opaque predicates, anti-debug, and section scrambling. v5 is about hardening the stub itself — the code that actually runs when the packed exe launches. three main additions: encrypted API strings, content-derived opmap obfuscation, and IAT wiping post-load.

quick recap

when you run a TinyLoad packed exe, the stub spins up a custom 32-opcode VM interpreter, executes a decryption bytecode program against the payload, then manually maps the original PE into memory and runs it. every packed file gets a randomly shuffled opcode table so the bytecode looks different every time.

v5 makes the stub itself harder to analyse statically.

1. encrypted API strings

in v4 the DLL and function names the stub needs — kernel32.dll, GetModuleHandleA, VirtualAlloc etc — were sitting as plaintext strings in the binary. any static analysis tool would immediately see what APIs the stub calls just from strings view.

v5 XOR-encrypts all of them:

static const BYTE _ed_k32[]  = {0x3A,0x37,0x21,0x3A,0x30,0x3A,0x64,0x6A,0x77,0x3E,0x37,0x30};
static const BYTE _ed_gmha[] = {0x70,0x5D,0x4D,0x77,0x54,0x58,0x48,0x52,0x5A,0x08,0x20,0x2C,0x27,0x28,0x20,0x07};
static const BYTE _ed_gpa[]  = {0x06,0x27,0x37,0x14,0x37,0x29,0x24,0x09,0x2D,0x2E,0x39,0x29,0x3E,0x3D};

each one decrypts at runtime with a rolling XOR:

static char* sdec2(char* buf, const BYTE* enc, size_t n, uint8_t k) {
    for (size_t i = 0; i < n; i++) buf[i] = enc[i] ^ (uint8_t)(k + i);
    buf[n] = 0; return buf;
}

no readable strings in the binary anymore. strings view on the packed output is clean.

2. opmap obfuscation via FNV hash

this one directly addresses feedback from the r/ReverseEngineering community who pointed out that the opmap decode table was sitting plaintext right behind the stub — effectively a silver platter for static analysis.

v5 derives a per-file XOR mask for the opmap using FNV-1a hash, seeded from the file’s own content:

static void xorOpmap(BYTE* opmap, const Tail& t, const BYTE* vmCode, const BYTE* pay) {
    uint32_t h = 0x811C9DC5u;
    auto feed = [&](uint8_t b) { h ^= b; h *= 0x01000193u; };
    for (int i = 0; i < 4; i++) {
        feed((uint8_t)(t.origSz >> (i * 8)));
        feed((uint8_t)(t.packSz >> (i * 8)));
        feed((uint8_t)(t.vmCodeSz >> (i * 8)));
    }
    DWORD vmLim = t.vmCodeSz < 32 ? t.vmCodeSz : 32;
    if (vmCode) for (DWORD i = 0; i < vmLim; i++) feed(vmCode[i]);
    DWORD payLim = t.packSz < 32 ? t.packSz : 32;
    if (pay) for (DWORD i = 0; i < payLim; i++) feed(pay[i]);
    for (int i = 0; i < NUM_OPS; i++) {
        feed((uint8_t)i);
        opmap[i] ^= (uint8_t)((h >> 24) ^ (h >> 16) ^ (h >> 8) ^ h);
    }
}

the hash feeds on origSz, packSz, vmCodeSz, and the first 32 bytes of both the VM bytecode and the payload. the resulting mask is different for every single packed file — you can’t extract the opmap from one sample and apply it to another. the stub runs xorOpmap at unpack time to recover the real table before passing it to the VM.

3. IAT wiping post-load

after the stub manually maps the packed PE into memory and resolves all its imports, v5 zeroes out the import structures:

// kill recovery
imp->OriginalFirstThunk = 0;
imp->Name = 0;
imp++;

// ...
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = 0;
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = 0;

once imports are resolved the IAT entries aren’t needed anymore. zeroing them means if someone dumps the process from memory after load, the import directory is gone — no DLL names, no function names, no reconstruction path from the dump alone.

4. dead code and junk VM instructions

v5 also adds dead code functions in the stub (__attribute__((used)) to force the compiler to keep them despite -O2) and junk NOP and self-mov instructions scattered through the VM bytecode to inflate and confuse disassembly:

eOp(bc,enc,NOP_I);              // junk
eOp(bc,enc,MOV_I); eR(bc,6); eR(bc,6); // junk — mov r6, r6

minor individually but they add noise on top of everything else.

current usage

TinyLoad.exe --i myapp.exe --vm --c

build from source:

g++ -o TinyLoad.exe TinyLoad.cpp -static -O2 -s

or grab the binary from releases.

if you run into files it doesn’t pack correctly, open an issue. and if you find it useful, a star helps a lot ❤️

repo: github.com/iamsopotatoe-coder/TinyLoad
blog + changelogs: iamsopotatoe-coder.github.io/TinyLoad/#blog

How I Built A Game to Help My Kids Learn

So, my kids’ math teacher sent home a YouTube video on place values and ascending order. It was good, but watching it wasn’t doing much. So over the weekend I built them a game instead.

It’s called Tumbling Towers — source on GitHub.

The Concept

You’re dealt two random cards, say 2 and 7. You can swap them to form 27 or 72, then drop that number into an empty slot in a tower. The rule: all blocks must be in ascending order from bottom to top. If you can’t place either combination without breaking that order, the tower falls and the game ends.

As levels go on, the tower gets taller. You start having to think about spacing and leave enough room between numbers, or you’ll paint yourself into a corner. It’s one thing to know 72 > 27. It’s another to decide where 27 belongs when you’ve already got 15 at the bottom and 60 in the middle.

The Stack

I wanted it working fast, so I used what I know:

  • React 19 & Vite 6
  • TypeScript
  • Tailwind CSS (v4): Went with a retro pixel-art look and custom utility classes like .pixel-border without leaving the component
  • Motion: When the tower falls, the blocks scatter. Both kids cheered the first time it happened, which told me the animation was worth it!
  • Local Storage for a high-score tracker

The Logic

The core validation checks that a block maintains ascending order when placed:

function isValidPlacement(tower: (number | null)[], index: number, number: number) {
  if (tower[index] !== null) return false;

  for (let i = 0; i < index; i++) {
    if (tower[i] !== null && tower[i] >= number) return false;
  }

  for (let i = index + 1; i < tower.length; i++) {
    if (tower[i] !== null && tower[i] <= number) return false;
  }

  return true;
}

Before each turn, we also check whether the game is already over, testing both digit combinations against every empty slot:

function canPlaceAnywhere(tower: (number | null)[], digits: [number, number]) {
  const num1 = digits[0] * 10 + digits[1];
  const num2 = digits[1] * 10 + digits[0];

  for (let i = 0; i < tower.length; i++) {
    if (tower[i] === null) {
      if (isValidPlacement(tower, i, num1) || isValidPlacement(tower, i, num2)) {
        return true;
      }
    }
  }
  return false;
}

The Result

They’ve been playing it a lot. The thing I wasn’t expecting: they started speaking their reasoning out loud. “I shouldn’t make 72. I only have one slot left at the top. I’ll make 27 and put it near the bottom.” That’s more than I got from the video.

Have you ever built something to teach your kids a concept? I’d love to hear about it in the comments. And if you found this useful, I drink too much coffee ☕

Most Traffic Spikes Are Predictable. So Why Are We Still Panic-Scaling?

The usual playbook when a big event is coming: someone sends a Slack message three hours before launch asking, “Did we scale up?” A senior engineer logs into the AWS console, eyeballs the current desired count, multiplies by something, and manually bumps the number. Then forgets to roll it back.

That’s the part nobody talks about. The spike passes, the instance count stays at 3x, and you’re burning money for two days because everyone assumed someone else would fix it.
We ran into this enough times that we built a proper way to handle it.

What it does

Event Readiness turns a planned traffic spike into a structured scaling plan. You define the event window, set an expected load multiplier, and attach the autoscaling groups you want to scale.

ZopNight handles the rest pre-scales before the event starts, holds capacity during it, and rolls everything back when it ends. No manual intervention. No forgetting to scale down.

How it works

You create a plan against your existing autoscaler policies:

  • Pick the event window (start, end, timezone)
  • Set a load multiplier — e.g., 3x for a campaign expecting 3x traffic
  • Attach target policies: AWS ASG, GCP MIG, or Azure VMSS
  • Set a pre-scale buffer (we default to 30 minutes before event start)

ZopNight calculates the scaled min/max/desired for each target before
you commit. If CPU metrics aren’t available, it tells you it’s estimating and why.

Before saving, a preview step shows you exactly what will happen to every target’s current size, the scaled size, and the timestamps the executor will fire. No surprises.

Once scheduled, the plan moves through a clear state machine:

draft → scheduled → scaling_up → active → scaling_down → completed

If something fails, it lands in failed, and you can retry from draft.
Cancelling from any active state rolls back whatever was already scaled.

The cost estimate

One thing we added that most teams don’t have: upfront cost visibility. Before you schedule the plan, you can see the estimated extra cost per target, per hour, in dollars. Not after the event.

Before it, for a plan running 8 hours at 3x capacity across two ASGs, that number is usually a lot smaller than the cost of the event going down.

How does your team handle planned traffic spikes right now? Manual scaling, scripts, or something else?

The Mid-Level Mindset

We started this series with a single paragraph from a fictional client. A vague brief about a tool where clients can submit requests and developers can track them. Nine articles later, we have a fully interrogated set of requirements, a collection of properly shaped features, a trusted data model, a system architecture diagram, a layered application design, and a sequenced build plan with individual tickets ready to pick up.

We have not written a single migration. We have not scaffolded a controller or configured a route. And yet the most important work is done.

That is the point this entire series has been building towards.

What Actually Changed

Think back to where we started. The instinct to open your editor the moment a brief lands. The pull towards action, towards visible progress, towards the feeling of productivity that comes from lines of code appearing on a screen.

That instinct is not wrong. It is just premature.

What changed over the course of this series is not the tools you have access to. Laravel was always capable of building Clarity. The database was always going to need an organisations table and a users table and a requests table with the right foreign keys. The layered architecture was always the right approach. None of that knowledge was new.

What changed is the order of operations. The willingness to invest thinking before typing. The discipline to ask “what do I not yet know?” before asking “how do I build this?”. That shift in sequence is what defines the move from junior to mid-level, and it compounds over time in ways that are hard to see until you look back.

What Mid-Level Actually Means

The industry uses the term loosely, but when I think about what a mid-level developer actually is, I come back to a few specific qualities.

A mid-level developer can be handed a brief and produce something buildable from it without constant direction. Not because they know every answer upfront, but because they know which questions to ask and how to find the answers before they start building.

A mid-level developer understands that the most expensive code to write is code that solves the wrong problem. They protect against that by doing the thinking work first.

A mid-level developer can communicate the shape of a problem to other people. They can draw a diagram, write a pitch, or explain a data model in plain language. That communication ability is what makes them valuable beyond their own output.

A mid-level developer knows when to apply a pattern and when to leave it out. They have moved past the phase of applying everything they have learned uniformly, and into the phase of using judgment about what a given situation actually needs.

None of those things are about programming language knowledge or framework familiarity. They are about thinking habits. And thinking habits are built through deliberate practice, not through reading documentation.

The Process in Brief

If you take one thing from this series, let it be this sequence. Apply it to every project you work on, every feature you are handed, every ticket that lands in your queue.

First, interrogate the requirement. Surface every unstated assumption. Ask the business questions before the technical ones. Write down what you know and what you do not know yet.

Second, define the actors and their needs. Write user stories with acceptance criteria. Make sure every feature has a clear definition of done that is anchored to a real user getting real value.

Third, shape the work. Decide how much it is worth before you estimate how long it will take. Draw the edges of what is included and write down what is explicitly out of scope.

Fourth, model the data. Draw the ERD before you touch a migration. Check every relationship for cardinality, every foreign key for direction, every array in a column for the related table it should be.

Fifth, draw the system. Show the major components, how they connect, where external dependencies sit, and where data flows. Write the plain-language description. If you cannot describe it clearly in prose, the diagram needs more work.

Sixth, sequence the build. Map the dependencies. Identify what has to exist before each feature can be built. Break each feature into discrete tickets with clear definitions of done.

Then build.

Where to Take This

This process is a foundation, not a ceiling. As you apply it to more projects, you will start to develop instincts about where the complexity hides in a brief, which ERD patterns recur across different problem domains, and which architectural decisions tend to cause the most pain if you get them wrong.

Those instincts are what senior developers have. They have run this process enough times, on enough different problems, that parts of it become automatic. They spot the ambiguous noun in a brief before the client finishes reading it to them. They see the missing pivot table in a data model before anyone has drawn it. They know from experience which features sound small and turn out to be enormous.

You build those instincts by doing the work deliberately, even when it feels slower than just opening your editor. Especially then.

A few resources worth spending time with as you continue:

Ryan Singer’s Shape Up is the deepest treatment of the shaping process I have found. It is free, it is short, and every developer who works on product software should read it.

Jeff Patton’s User Story Mapping goes further on the user story side than we covered here, and is particularly useful when you are working on complex products with many different user types.

John Ousterhout’s A Philosophy of Software Design is the best writing I know of on the question of where complexity comes from and how to fight it. It will change the way you think about every design decision you make.

And then there is the work itself. Take a project, real or fictional, and run the full process from brief to build plan before you write the first line of code. Do it again on the next one. The process becomes faster with practice, but it never stops being valuable.

A Final Word on Clarity

We built something real in this series. Clarity started as a vague paragraph and became a fully designed application with a clear data model, a considered architecture, and a sequenced plan ready to execute.

We never wrote a migration. We never wrote a controller. But anyone who has followed this series could pick up the build plan from article nine and start building Clarity today, with confidence that the foundations are solid and the direction is clear.

That confidence, grounded in thinking rather than assumption, is what the mid-level mindset feels like from the inside.

It is worth developing.

Thank you for following along with From Requirements to Reality. If this series helped you think differently about how you approach a problem before you start building, that is exactly what it was designed to do.

Hastanelerde “Rapor Başvuru Merkezi” kurulacak

Sağlık Bakanlığınca hazırlanan “Sağlık Raporları Yönetmeliği” ile ikinci ve üçüncü basamak sağlık hizmetlerinde sağlık raporu işlemlerinin tek bir noktadan başlatılması, takip edilmesi ve sonuçlandırılması sağlanırken, kişilerin hastanede devam eden rapor süreçlerine ilişkin bilgiye kolay erişimi mümkün hale getirildi
– Bu amaçla sağlık kurumlarında rapor başvuru merkezleri oluşturulacak ve süreç bu merkezler üzerinden takip edilebilecek