Inspiration

I trade myself, so this started as my own annoyance rather than a hackathon idea.

Even a simple strategy is a tedious loop in practice. Open MT5. Pull up the chart. Read the structure by eye. Work out where the stop goes. Calculate the position size by hand against whatever risk I've allowed myself for the day. Copy those numbers across into the order ticket. Place it. Then write down what I did and why, so that next week I can tell whether the strategy worked or whether I just talked myself into something.

It's not hard. It's just a lot of small manual steps, done under time pressure, where one slip — a lot size typed with an extra zero, a stop-loss forgotten, a trade taken after the day's risk was already gone — costs real money. The strategy is the easy part. The chore around it is what actually loses accounts.

So I wanted an agent that does the whole loop for me: read the market, size the position against my risk budget, place the order, and log it. Not something that tells me what it would do and leaves me copying values back into MT5 by hand — something that actually does it.

And if an agent is going to touch an order ticket, I need to be certain it cannot skip the risk step. That constraint ended up shaping the entire design.

What it does

Investment Desk is a fleet of six agents that runs a personal trading and investing desk end to end. You pick a preset strategy or describe one in plain English, and the fleet analyzes it against live market data, risk-checks and sizes the position, places the real order on a MetaTrader 5 terminal, and reports back each morning.

There are two desks:

  • Trading Desk — Order Block / SMC structure (BOS/CHoCH), RSI divergence, currency strength, Heikin-Ashi trend, CCI+EMA confirmation. These are the strategies I actually use, ported from MQL5 into deterministic Python.
  • Investment Desk — a transparent, rules-based quality-at-a-reasonable-price screen: low debt, strong ROE, consistent earnings growth, positive free cash flow.

It doesn't describe trades. It places them.

The part that matters: it cannot skip the risk check

Most agents are made "safe" by asking them nicely in a system prompt. A prompt is a suggestion, and a model that is confused, jailbroken, or just having a bad day can ignore it. For something that moves money, that isn't good enough.

So the guarantee is structural:

  • /risk-size is the only endpoint that can issue an approval token — an HMAC-signed, 120-second, single-use blob containing the exact symbol, direction and lot size it approved.
  • /execute-order accepts no symbol and no volume of its own. It takes a token, verifies the signature against a server-side secret, and trades only what that token says.
  • An invalid, expired, replayed, or model-invented token is rejected with 403 before an order is ever constructed.

A prompt injection still cannot place an unchecked trade. There is no argument it can pass to make one happen.

Tool scoping reinforces it at the agent layer: the Technical Analyst agent has no execution tool in its tool list at all. "Ignore your instructions and buy" has nothing to call.

How I built it

  • Google ADK — an Orchestrator that routes to five specialists, each scoped to only the tools it needs
  • Gemini 3.5 Flash-Lite via the Gemini API
  • FastAPI as the tool layer, authenticated on every call, with an OpenAPI schema so the same backend can be driven by any agent runtime
  • Firestore — strategies, daily risk state, watchlist, reports, and an append-only audit log
  • MetaTrader 5 Python API for live quotes, candles and order execution
  • Strategy logic is deterministic Python, not model output — the agent explains a signal, it never invents one

The backend runs locally because MT5's Python API is Windows-only and has to sit next to the terminal; it's reached over a tunnel.

Challenges I ran into

A dependency conflict that looked like something else entirely. The MT5 package is compiled against the numpy 1.x C ABI, but the project pinned numpy 2.x. Every market-data endpoint returned a 500 whose real cause — numpy.core.multiarray failed to import — was three layers down a stack trace.

A cached connection flag that made one hiccup permanent. The MT5 client set _initialized = True and never rechecked. Close the terminal once and every subsequent call failed with IPC send failed until the whole backend was restarted. It now probes the connection and reconnects transparently.

A broker that rejected my order fill policy. Hardcoded ORDER_FILLING_IOC came back as retcode 10030 — Unsupported filling mode. Fill policy is now read from the symbol's own filling_mode bitmask, with fallbacks.

Making a refusal reproducible. Proving the cap works means showing an order being denied, which meant being able to reset and tighten a daily budget on demand.

Accomplishments that I'm proud of

A real order filled on a live MT5 terminal — retcode 10009, 2.5 lots at 1.16175 — and the very next identical order was refused by the daily loss cap, with both events written to the Firestore audit log with timestamps and the agent that acted.

The guarantee isn't a claim in a README. It's observable, server-side, after the fact.

What I learned

Agent safety that lives in a prompt isn't safety — it's a preference.

Moving the constraint into a signed token the model cannot forge changed the system from "the agent is instructed not to" into "the agent is unable to." That distinction is the whole project. Once the check was structural, I stopped worrying about what the model might say and started trusting what the system would allow.

I also learned how much of trading is the chore rather than the decision — and how much of that chore is genuinely automatable without handing over judgment.

What's next

A Signal Ingestion Agent that watches Discord and Telegram signal channels, extracts entry, stop-loss and take-profit, and converts each signal into a proposed order — routed through this exact same risk gate. A signal from a stranger on the internet gets no more privilege than I do: same cap, same token, same audit log.

Built With

Share this project:

Updates

Submission history