Inspiration

My quantitative trading engine runs 24/7 unattended. Checking on it, or shutting it off fast during a volatile market, normally means SSHing into a server, which is slow exactly when speed matters most. I wanted that down to a text message.

What it does

TradeOps Agent is a conversational control layer for a trading engine, reachable on Telegram and Slack. Send status, pnl, or positions and it answers instantly from live state, formatted as a native card on each platform. Send pause and it flips a flag the engine checks before opening new positions, immediately, deterministically. Send resume to release it. Ask anything else in plain language, "how's my book looking," "any risk right now," and an LLM answers using the current state as its only source of truth.

One deliberate design choice: pause and resume are hardcoded keyword checks, never touched by the LLM. An emergency stop cannot be probabilistic. Everything flexible gets the model's help; the one thing that has to be certain doesn't.

How we built it

Caspian SDK handles the multi-channel part: one @client.on_message handler processes every incoming message regardless of whether it arrived via Telegram or Slack, so there's no duplicated per-channel logic anywhere in the codebase.

State lives in Render's managed Key Value store (Redis-compatible), not local files, specifically so it survives redeploys and free-tier spin-downs. Status and PnL replies use Caspian's rich message blocks, rendering as a formatted card on Telegram and native Block Kit on Slack from the exact same code.

Free-text questions go through Featherless (OpenAI SDK-compatible), with the full state JSON passed as grounded context and a system prompt that scopes the model to only answer from that data.

Challenges we ran into

Mid-build, my GitHub account got flagged by their abuse-detection system, which silently blocked every OAuth-based deploy for days with no clear error, just failed git clones. Rather than wait on a support ticket, I mirrored the whole repo to GitLab and rebuilt the deploy pipeline there.

Separately, an early version kept state in a local JSON file on the deployed instance. Testing a restart scenario made it obvious that a free-tier redeploy wipes local disk, meaning a "paused" flag could silently disappear and trading would resume with nobody telling it to. That's a dangerous failure mode for a kill switch specifically, so state moved into Redis instead, and I confirmed the fix by actually restarting the live service mid-pause and checking it held.

Also caught during testing: the LLM once answered "what is the time" with a nonsense value, because last_scan was the only date-shaped field in its context and it reached for it anyway. Fixed by explicitly scoping the system prompt to only status/pnl/positions questions and naming what each field means, rather than trusting the model to infer scope on its own.

What's next

Right now the demo state is seeded manually rather than fed by a live engine, worth saying plainly rather than implying otherwise. The natural next step is wiring the real EC2 trading engine to write into the same Redis key on its own polling cycle, so the numbers move on their own instead of being set once for a demo.

Built With

Share this project:

Updates

Submission history