FIX 4.4
Connect an execution algo to the simulated venue over a real FIX session.
HTTP endpoints on this page
- GET/fix/usage
Parameters and response shapes for each are in the API reference.
Pulse exposes its simulated exchange as a FIX 4.4 acceptor over raw TCP. Your algo logs on with its own FIX engine, starts a simulation, subscribes to the book, and sends orders into the live agent-based market — the same message types it would speak to a real venue. Nothing in your order-entry code needs to know it is talking to a simulation.
For why you would do that rather than submit a batch job, see the FIX overview. This page is the technical reference: connection details, identity, the message set, and the custom tags.
FIX trading requires a Pro or demo tier account. Entitlement is checked live at every logon from the key you present, so an upgrade or downgrade takes effect on your next session with no coordination needed.
Connecting
The acceptor is a plain TCP listener, not an HTTP endpoint, so it sits on its own address rather than behind the API host.
| Setting | Value |
|---|---|
| BeginString | FIX.4.4 |
| Port | 5001 |
| HeartBtInt | 30 seconds |
| Session hours | 00:00:00–23:59:59 — the venue is always open |
| ResetOnLogon | Y — sequence numbers restart every session, so you never need to resend or reset by hand |
| Data dictionary | Standard FIX 4.4, with user-defined fields permitted (see custom tags below) |
Ask support@simudyne.com for the venue address and to have your account enabled for FIX.
Identity and authentication
Your CompID is your user_id
There is nothing to provision and no second credential to keep in step. Your SenderCompID is your Pulse account id, which you can read straight off your profile:
me = client.profile.get()
print(f"SenderCompID: {me['user_id']}")
print(f"tier: {me['tier']}")SenderCompID: user_76b8ee0fe28cdbbf42ebc0e763c76750
tier: proThe venue's CompID
Set TargetCompID to PULSE. The venue announces that name on every message it sends you, so a session configured for a different venue cannot log on by accident.
TargetCompID (56) = PULSEThe API key rides in the Logon
Put your Pulse API key in Password(554) on the 35=A Logon. A FIX credential is presented once and the session then lives for hours, so this is the only point at which you prove who you are — a rejected logon refuses the session outright rather than letting an unknown client trade.
8=FIX.4.4|35=A|49=user_76b8ee0f…|56=PULSE|98=0|108=30|554=pk_live_…|Revoke the key and the session dies with it — there is no separate FIX credential to chase. That makes a dedicated key per algo the natural setup: revoking one stops that algo and nothing else.
Why a logon is rejected
| RejectLogon text | Cause |
|---|---|
| missing Password(554): send your Pulse API key | The Logon carried no key |
| API key rejected (401) / (403) | The key is unknown, revoked or expired, or the account is inactive |
| this API key belongs to a different account | The key is valid but resolves to a different account than the SenderCompID claims. A valid key cannot be used to trade as somebody else |
| tier 'free' may not trade over FIX | The account is not on a tier permitted to run simulations |
| <CompID> is not a provisioned CompID | The venue has no session declared for that account — it is brand new. The session list is rebuilt periodically; contact support if it persists |
| authentication is temporarily unavailable | The venue could not reach the account store. It fails closed rather than admitting everyone |
A session, end to end
The one step that is easy to miss is the first: you must start a simulation before there is any market to subscribe to. A 35=V sent before that comes back as 35=Y MarketDataRequestReject.
| Step | You send | You get back |
|---|---|---|
| 1. Log on | 35=A with your key in 554 | 35=A, or RejectLogon |
| 2. Start a simulation | 35=g with the symbol, date and speed in custom tags | 35=h TradingSessionStatus, Open (2) |
| 3. Subscribe | 35=V MarketDataRequest | 35=W, a 10-level book snapshot |
| 4. Watch | — | 35=X incremental refreshes: book moves and trades, stamped with simulation time |
| 5. Trade | 35=D NewOrderSingle | 35=8 ExecutionReport — New, then fills |
| 6. End | — | 35=8 DoneForDay, then 35=h Closed (3) carrying the cost report |
Simulation time, not wall time, is what the market data is stamped with. Tag 20010 carries it as a display string and 20011 as seconds, with 20012 giving the time-of-day the clock started — enough to plot a session on its own axis regardless of how fast you ran it.
Message set
| MsgType | Name | Direction | Purpose |
|---|---|---|---|
| 35=A | Logon | → | Authenticate with your API key in Password(554) |
| 35=g | TradingSessionStatusRequest | → | Start a simulation, or change the speed of a running one |
| 35=h | TradingSessionStatus | ← | Open (2) when the simulation is live, Closed (3) when it ends. The final one carries the cost report |
| 35=V | MarketDataRequest | → | Subscribe to the book for a symbol |
| 35=W | MarketDataSnapshotFullRefresh | ← | A 10-level book snapshot |
| 35=X | MarketDataIncrementalRefresh | ← | Book and trade updates, throttled and simulation-time stamped |
| 35=Y | MarketDataRequestReject | ← | You subscribed with no simulation running — send 35=g first |
| 35=D | NewOrderSingle | → | Market or limit order into the live market |
| 35=8 | ExecutionReport | ← | New, partial fill, filled, and DoneForDay at the end |
| 35=9 | OrderCancelReject | ← | Cancel and amend are not supported; the order stands |
| 35=j | BusinessMessageReject | ← | An application message the venue does not handle |
Orders
40=2 Limit with a price in 44 rests in the book. Anything else — including an omitted OrdType — is treated as a market order and executes against resting liquidity immediately.
Cancel and amend are not implemented. An OrderCancelRequest or OrderCancelReplaceRequest is answered with 35=9 OrderCancelReject and the original order is left working. Size your child orders on the assumption that once sent, they stand.
Custom tags
The venue permits user-defined fields, and uses them to carry the simulation parameters FIX has no standard field for. These go on 35=g:
| Tag | Field | Default | Description |
|---|---|---|---|
| 20001 | speed | 1 | Simulation clock multiplier. 1 is real time |
| 20002 | symbol | SIM | Instrument to simulate |
| 20003 | seed | 42 | Random seed — the same seed reproduces the same market |
| 20004 | horizon_s | 600 | How long to run, in simulation seconds |
| 20005 | provider | — | Data provider, e.g. omd or bmll |
| 20006 | exchange | — | Exchange protocol, e.g. hkex_securities |
| 20007 | ref_price | — | Reference price to start from |
| 20008 | cal_date | — | Calibration date the market is built from |
| 20009 | scenario | normal | Market scenario to inject — the same set as a batch run |
Use calibrated_data() to pick a symbol, provider, exchange and cal_date combination that is actually calibrated, and list_scenarios() for the scenario names.
Outbound
| Tag | Field | On | Description |
|---|---|---|---|
| 20010 | sim_time | 35=W / 35=X | Simulation time as a display string |
| 20011 | sim_secs | 35=W / 35=X | The same instant as seconds, for plotting |
| 20012 | day_open | 35=h | Time of day the simulation clock starts, as seconds since midnight |
Speed
Send 35=g again on a running session with a new 20001 to change speed mid-simulation. Around 10× is what the engine sustains in practice — above that the clock is limited by how fast the market can actually be generated, not by the throttle.
The end-of-session cost report
When the simulation ends, the final 35=h Closed carries your execution costs, measured against a baseline twin of the same simulation that never saw your orders. That is what makes the number meaningful: it is the cost of your flow specifically, not of the day.
| Tag | Field | Description |
|---|---|---|
| 20030 | executed_vol | Total volume executed |
| 20031 | avg_exec_prc | Volume-weighted average execution price |
| 20032 | market_slippage_bps | Slippage against the reference price, in basis points |
| 20033 | market_risk_bps | Timing risk in basis points |
| 20034 | market_impact_bps | Impact against the baseline twin, in basis points |
The same figures also arrive as JSON in Text(58) on that message, so a client that does not want to read custom tags can parse one field instead. The batch equivalent is exec_results.parquet — see Execution algorithms.
What you have run
The venue records the shape of every session it starts, which you can read back over HTTP with client.fix.usage(). Only the parameters are kept — symbol, date, scenario, speed, horizon. No orders, no schedules, no fills: that is your strategy, and it is not stored.
usage = client.fix.usage()
print(f"{usage['total_runs']} FIX runs, "
f"{usage['simulated_seconds']:.0f} simulated seconds")
for run in usage["runs"][:5]:
print(f" {run['symbol']} {run['cal_date']} {run['scenario']}: "
f"{run['runs']}x")12 FIX runs, 37800 simulated seconds
1024 2025-09-01 normal: 9x
1024.HK 2025-10-14 normal: 1x| Field | Type | Description |
|---|---|---|
| total_runs | int | Sessions recorded |
| simulated_seconds | float | Total horizon across those sessions |
| by_symbol / by_scenario / by_cal_date | dict | Run counts grouped each way |
| runs | list | One {symbol, cal_date, scenario, runs} per distinct combination, most-run first |
| last_run_at | str | null | When the most recent session started |
| truncated | bool | True if the history hit the server's cap |
Getting access
You need a Pro or demo tier account, an API key, and the venue address. Email support@simudyne.com telling us which system you want to connect and we will confirm the address and check your account has a session declared.
A brand-new account needs the venue's session list rebuilt before it can log on, because a FIX engine will not accept a Logon for a session it was never configured with. Everything else — tier changes included — applies immediately, with no restart.