← All projects

Discord → NinjaTrader 8 Signal Executor

A fully local pipeline that reads trade calls from two Discord channels, parses and validates them, and executes them in NinjaTrader 8 through ATM strategies on the right account.

Client
Futures signal group
Year
2025
Category
Integrations
Stack
  • NinjaTrader 8
  • C#
  • NinjaScript
  • Python
  • discord.py
  • Flask
  • JSON
  • YAML

The problem

A signal group posted futures trade calls in Discord and wanted them placed in NinjaTrader 8 automatically. Everything had to run on the trader’s own machine, with no cloud service, no third-party copier and no NT8 ATI. Each channel had to map to its own NT8 account and ATM template.

What I built

  • Discord bot (discord.py) that watches two channels. It queues messages while disconnected, replays them on reconnect and runs a health-check loop.
  • Local Flask bridge with /signal, /status, /health, /market-prices and /stats endpoints. It writes a JSON signal file that NT8 picks up.
  • Signal parser built on regexes for the group’s command set:
    • BUY/SELL n SYMBOL STOP LIMIT @ price
    • CLOSE n SYMBOL and CLOSE POSITION
    • MOVE SL TO price
    • cancel all or cancel n pending orders
  • Validator that checks quantity range, price precision and the symbol pattern. It also applies a rate limit, a daily trade cap and a max position size.
  • Order-type detection: NT8 publishes the last price back to the bridge. The bridge then decides whether an entry should be a LIMIT or a STOP LIMIT, whatever the message said.
  • NinjaScript executor that routes each signal to the channel’s account and ATM template, submits with OrderEntry.Manual, and writes a processed flag back to the file so no signal runs twice.
  • ATM tracking file so open ATM instances survive an NT8 restart.
  • One-command launcher that starts the bot and the bridge, frees the port if needed, and logs JSON with daily rotation.

Details

Most of the work was in NT8’s ATM API edge cases:

  • ATM orders are invisible to account.Orders. Stops and targets are looked up one by one by name (Stop1..5, Target1..5) for each ATM ID.
  • One-tick delay. ATM creates its stop and target a tick after the entry fills, so MOVE SL retries until the orders exist.
  • Partial closes. One ATM instance is opened per contract, so CLOSE 1 can close a single instance and its own brackets.
  • Reversal bug. AtmStrategyClose reversed a position that was already partly closed. That case now uses a manual flatten plus cancelling the brackets.
  • Dynamic ATM templates. AtmStrategyCreate has no quantity parameter. The executor clones the parent template’s XML, scales each bracket quantity to the requested size and deletes the temporary templates on shutdown.
  • Strategy instead of add-on. The first build was an AddOnBase add-on. The ATM stop-modification calls only exist on strategies, so production runs as a strategy that polls the signal file on a timer.