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.
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-pricesand/statsendpoints. 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 @ priceCLOSE n SYMBOLandCLOSE POSITIONMOVE 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 aprocessedflag 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 SLretries until the orders exist. - Partial closes. One ATM instance is opened per contract, so
CLOSE 1can close a single instance and its own brackets. - Reversal bug.
AtmStrategyClosereversed a position that was already partly closed. That case now uses a manual flatten plus cancelling the brackets. - Dynamic ATM templates.
AtmStrategyCreatehas 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
AddOnBaseadd-on. The ATM stop-modification calls only exist on strategies, so production runs as a strategy that polls the signal file on a timer.