How to Build an FX Trading Bot with Hermes and FXMacroData
Author: FXMacroData Team
Published: May 21, 2026
Hermes is useful for FX automation when the job is tightly bounded: read structured macro data, compare it with price context, and return a decision that a separate risk layer can accept or reject. In this guide, you will build a local Hermes-powered research bot for USD/JPY that produces trade alerts, not live orders.
The finished bot will combine US inflation, Federal Reserve policy rate, Bank of Japan policy rate, spot FX context, and event-risk checks from the release calendar. The important design choice is separation: FXMacroData supplies clean inputs, Hermes writes the thesis, and your own code enforces risk.
Macro releases, policy rates, calendar events, and spot FX from FXMacroData.
Hermes converts structured inputs into a concise directional thesis.
Schema validation, event filters, size caps, and a human-readable alert.
Prerequisites
- Python 3.10 or newer.
- An FXMacroData API key from API Management.
- A local or hosted Hermes endpoint, for example Hermes through Ollama.
- Basic comfort with REST APIs and environment variables.
pip install requests python-dotenv pydantic
export FXMD_API_KEY="YOUR_API_KEY"
export HERMES_URL="http://localhost:11434/api/generate"
Step 1: Set a narrow bot mandate
Do not start with a multi-pair autonomous trader. Start with a research assistant whose only job is to say whether the current USD/JPY setup is worth reviewing.
| Design choice | First version | Reason |
|---|---|---|
| Pair universe | USD/JPY only | Policy divergence and intervention risk are easy to reason about explicitly. |
| Execution | Alert-only | Prevents model text from becoming an order ticket. |
| Output | JSON decision | Lets your code validate every field before anyone sees the alert. |
| Risk limit | Maximum 0.5% suggested risk | Keeps the model inside a fixed control boundary. |
Step 2: Pull only the inputs the model needs
Start with three endpoint calls: the latest US inflation release, the latest US policy-rate context, and the latest Japan policy-rate context. Add spot FX as the market confirmation layer.
curl "https://api.fxmacrodata.com/v1/announcements/usd/inflation?api_key=YOUR_API_KEY"
curl "https://api.fxmacrodata.com/v1/announcements/usd/policy_rate?api_key=YOUR_API_KEY"
curl "https://api.fxmacrodata.com/v1/forex?base=USD"e=JPY&api_key=YOUR_API_KEY"
In Python, keep the data client small. If a request fails, stop the signal loop rather than asking Hermes to reason over incomplete context.
import os
import requests
API_BASE = "https://api.fxmacrodata.com/v1"
API_KEY = os.environ["FXMD_API_KEY"]
def fxmd_get(path, **params):
response = requests.get(
f"{API_BASE}{path}",
params={"api_key": API_KEY, **params},
timeout=25,
)
response.raise_for_status()
return response.json()
Step 3: Normalize the trading context
Hermes performs better when the prompt receives a compact context object instead of raw endpoint payloads. Collapse each API response into the few fields that affect the decision.
def latest_row(payload):
rows = payload.get("data", [])
return rows[-1] if rows else {}
context = {
"pair": "USD/JPY",
"usd_inflation": latest_row(fxmd_get("/announcements/usd/inflation")),
"usd_policy_rate": latest_row(fxmd_get("/announcements/usd/policy_rate")),
"jpy_policy_rate": latest_row(fxmd_get("/announcements/jpy/policy_rate")),
"spot": fxmd_get("/forex", base="USD", quote="JPY").get("data", [])[-5:],
"risk_rules": {
"allowed_actions": ["long", "short", "flat"],
"max_size_pct": 0.5,
"requires_invalidation": True,
},
}
Rates, inflation, and surprise direction define the fundamental pressure.
Recent USD/JPY levels show whether the market is already validating the thesis.
Upcoming CPI, NFP, PCE, or central-bank events can override the signal.
Step 4: Force a strict decision contract
The model should return a machine-checkable object. Keep the schema short enough that a human can scan it and strict enough that your code can reject malformed output.
{
"action": "long | short | flat",
"confidence": 0.0,
"thesis": "one concise sentence",
"invalidation": "specific market or macro condition",
"size_pct": 0.0,
"next_data_to_watch": "release or event"
}
import json
prompt = f"""
You are an FX research assistant.
Use this context: {json.dumps(context)}
Return JSON only. No prose outside JSON.
Choose action from long, short, or flat.
Keep size_pct at or below 0.5.
Include a concrete invalidation condition.
"""
This is where many bot examples go wrong: they ask the model for a complete trading system. You are asking for one bounded judgment that your application can review.
Step 5: Call Hermes and reject bad output
The Hermes call is small. The validation step is the important part: if the response is not valid JSON or violates your limits, the bot should return no trade.
import requests
hermes_request = {
"model": os.environ.get("HERMES_MODEL", "hermes3"),
"prompt": prompt,
"stream": False,
}
response = requests.post(
os.environ["HERMES_URL"],
json=hermes_request,
timeout=45,
)
response.raise_for_status()
decision = json.loads(response.json().get("response", "{}"))
if decision.get("action") not in {"long", "short", "flat"}:
decision = {"action": "flat", "thesis": "Rejected invalid action"}
if float(decision.get("size_pct", 0)) > 0.5:
decision["action"] = "flat"
decision["size_pct"] = 0.0
decision["thesis"] = "Rejected oversized risk request"
Step 6: Add event and stale-data gates
Before turning a decision into an alert, run hard filters that do not depend on the model. Two gates matter most for a first version.
| Gate | Trigger | Bot response |
|---|---|---|
| Event proximity | High-impact release within the next 30 minutes | Force flat or reduce size to zero. |
| Stale macro data | Latest required field is missing or older than expected | Skip alert and log the missing input. |
| Regime disagreement | Macro thesis and spot momentum conflict | Downgrade confidence and ask for confirmation. |
You can expand the event filter with US Non-Farm Payrolls, US Core PCE, Japan inflation, and USD COT positioning once the single-pair loop is stable.
Step 7: Send an alert, not an order
The first production version should send a message to Slack, Telegram, email, or a private dashboard. A clean alert separates the model's thesis from your risk controls.
[FX BOT] USD/JPY
Action: LONG
Confidence: 0.72
Size: 0.35% risk, capped by rule
Thesis: Policy divergence still supports USD/JPY.
Invalidation: Daily close below the defined support zone.
Watch next: US Core PCE and BoJ communication.
Optional: connect Hermes through MCP
If your agent runner supports the Model Context Protocol, you can expose FXMacroData as a tool instead of writing direct REST calls in every bot script. That is useful when Hermes sits inside a broader agent shell with tool-routing support.
{
"servers": {
"FXMacroData": {
"type": "http",
"url": "https://mcp.fxmacrodata.com"
}
}
}
A good MCP prompt is still narrow:
Use FXMacroData tools to review USD/JPY.
Check US inflation, US policy rate, Japan policy rate,
latest USD/JPY spot context, and upcoming calendar risk.
Return only the decision JSON contract.
Common mistakes to avoid
- Letting the model create or change risk limits.
- Passing entire raw API responses into the prompt when a small context object is enough.
- Skipping stale-data checks because the model produced a confident answer.
- Testing on too many pairs before one pair has a clean audit trail.
- Treating model confidence as a probability of profit.
What you built
You built a Hermes-powered FX research loop with structured FXMacroData inputs, a compact decision schema, hard risk gates, and alert-only output. The next useful extension is not auto-execution. It is a daily replay job: run the bot over each London morning setup, record the decision, and compare the model's thesis with what happened after the next major release.