The jinghong black-and-white Silkie mascot

C++ / execution infrastructure

jinghong order router Intent in.
Orders out.

A standalone C++ process that turns multi-source position intentions into broker market orders. Its first execution target is Alpaca; the upstream intent contract is broker-neutral. The book stays honest as sources change, expire, or fail.

  • Market orders only
  • Heartbeat aware
  • Cancel + replan
  • Local-first status

The narrow waist of the trading stack.

Robots state the positions they want. jinghong owns reconciliation, execution state, and operator controls. Market marks stay on their own path, so the order router remains focused and predictable.

01 / PRODUCERS

Robots + Hyperion

Publish signed position intents and liveness heartbeats for one account.

JSON payloads
02 / BUS

goblin-core

Preferred RESP Pub/Sub transport over a local Unix domain socket.

PUBLISH / SUBSCRIBE
03 / ROUTER

jinghong

Aggregates intent, compares actuals, plans deltas, and exposes HTTP control.

C++ process
04 / BROKER TARGET

Execution target

Alpaca ships first: persistent HTTPS for commands and trade_updates WebSocket for order state.

REST + WSS
goblin-slurp publishes marks
T: / Q: market data bypasses jinghong
Hyperion consumes marks
01 / INTENT

State the destination.

Producers publish desired signed share counts, not imperative buy and sell commands.

02 / AGGREGATE

Many sources, one target.

Unmuted source positions are summed per symbol into a single account-level book.

03 / RECONCILE

Trade only the net delta.

Actual holdings are compared with the aggregate target before an order is planned.

04 / RECOVER

Fills drive local truth.

Trade updates maintain actual quantity and cash; stale in-flight orders are polled.

05 / CONTROL

Freeze and flatten separately.

Block stops new trades without changing holdings. Liquidate closes and then blocks.

06 / EXPIRE

Silence removes stale demand.

After the heartbeat TTL, a source is zeroed and the aggregate book is reconciled again.

Reconciliation, with hard edges.

jinghong deliberately keeps its execution vocabulary small. The process accepts target state, emits market orders, and makes cancellation, direction flips, and operator intervention explicit.

  1. 01

    Aggregate live sources

    Sum every unmuted producer on this process’s ORDER channel. A zero position clears only that source’s symbol claim.

  2. 02

    Compare target with actual

    Use bootstrapped positions plus applied fill events to determine the smallest market-order delta.

  3. 03

    Respect frozen state

    Global or symbol blocks suppress new orders while the intended book continues to update for inspection.

  4. 04

    Cancel when intent moves

    If aggregate demand changes during an open order, request cancellation and re-plan one order against the new net target.

  5. 05

    Stop flips at zero

    A long-to-short or short-to-long transition first flattens, then opens the opposite side after the terminal event.

  6. 06

    Fan status back out

    Lifecycle, intent, reconcile, order, fill, heartbeat, lock, and error events are published for Hyperion and monitors.

Prefer the bus we built.

jinghong speaks RESP through hiredis, so a Redis-compatible server can satisfy the contract. In the Goblin Reactor stack, use goblin-core: it is ours, it supports the Pub/Sub surface directly, and it keeps the order path inside the same systems family.

Meet goblin-core →
# Typical local transport
$ export JINGHONG_REDIS_PATH=/run/goblin-core/goblin.sock

# The wire contract stays familiar
> SUBSCRIBE ACCOUNT:ORDER:paper3
> PUBLISH ACCOUNT:STATUS:paper3 {...}

# RESP over UDS; no SBE required

Redis is compatible. goblin-core is recommended. jinghong currently uses the RESP command path—not goblin-core’s optional SBE transports.

Build it from source.

jinghong is a C++20 executable built with CMake 3.20 or newer. Install the system libraries, clone the repository, and produce a release binary with the commands below.

Compiler GCC or Clang C++20 support
Build CMake version 3.20+
HTTP libcurl broker REST
TLS OpenSSL REST + WSS
RESP hiredis goblin-core UDS
JSON nlohmann/json header-only
Debian / Ubuntu prerequisites
sudo apt-get update
sudo apt-get install -y \
  git \
  build-essential \
  cmake \
  libcurl4-openssl-dev \
  libssl-dev \
  libhiredis-dev \
  nlohmann-json3-dev

# Package names vary on other distributions.
clone / configure / build / install
git clone https://github.com/adamdeprince/jinghong.git
cd jinghong

# Configure an optimized build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

# Compile on all available cores
cmake --build build -j"$(nproc)"

# Confirm the local binary
./build/jinghong --help

# Optional: install to /usr/local/bin/jinghong
sudo cmake --install build
Build result

The local executable is build/jinghong. After sudo cmake --install build, invoke it as jinghong from your PATH. A running goblin-core RESP socket is a runtime dependency, not a compile-time one.

Optional build Compile the goblin-core shared-memory ring client

Point CMake at the goblin-core headers. This option still uses RESP; do not enable SBE for jinghong’s command path.

cmake -S . -B build \
  -DCMAKE_BUILD_TYPE=Release \
  -DJINGHONG_WITH_GOBLIN_RING=ON \
  -DJINGHONG_GOBLIN_INCLUDE=/path/to/goblin-core/include
cmake --build build -j"$(nproc)"

Connect one account.

This example starts the paper3 process on HTTP port 8768. Use one unique channel pair for every jinghong instance.

bash / configure + run
# Current target: Alpaca credentials
export ALPACA_KEY=...
export ALPACA_SECRET=...
export ALPACA_API=https://paper-api.alpaca.markets/v2

# One process, one ORDER channel
./build/jinghong \
  --redis /run/goblin-core/goblin.sock \
  --order-channel ACCOUNT:ORDER:paper3 \
  --status-channel ACCOUNT:STATUS:paper3 \
  --http-port 8768

# Verify the local control plane
curl -sS http://127.0.0.1:8768/v1/status
--redis RESP Unix socket. Default: /tmp/jinghong-redis.sock.
--order-channel Inbound intent channel. Never share it between processes.
--status-channel Outbound lifecycle and book events.
--http-port Control plane port. Default: 8765.
--heartbeat Source silence TTL in seconds. Default: 15.
--dry-run Skip broker networking and simulate market fills.

Bootstrap, inspect, intervene.

Read endpoints return local book state unless their name explicitly says refresh. Command endpoints mutate intent or safety controls and trigger reconciliation as needed.

Default base http://127.0.0.1:8765
Content type application/json · OPTIONS → 204
Security boundary No authentication. Bind only to a trusted network.
Response convention

Successful commands return HTTP 200 with "ok": true. Invalid JSON or missing required fields returns 400; unknown routes return 404. Every response includes permissive CORS headers.

Read endpoints

Local by default
GET /v1/status Full local execution snapshot

Returns aggregate intent, actual positions, in-flight orders, cached account values, sources, channels, and all lock state.

No broker request: this endpoint is designed for fast UI bootstrap and polling from local state.

Aliases: GET /status

200 / abbreviated response
{
  "ok": true,
  "paper": true,
  "order_channel": "ACCOUNT:ORDER:paper3",
  "intended": { "IBM": 50 },
  "actual": { "IBM": 20 },
  "inflight": {
    "IBM": {
      "side": "buy",
      "qty": 30,
      "filled_qty": 0
    }
  },
  "locks": {
    "global_block": false,
    "blocked_symbols": [],
    "muted_sources": []
  },
  "sources": [],
  "ts_ms": 1753891200000
}
GET /v1/holdings Non-zero local actual positions

Returns a compact symbol-to-share-count object from local actual state. Zero positions are omitted.

No broker request: use the refresh endpoint when a full broker resync is required.

Aliases: GET /holdings, GET /

200 / response
{ "IBM": 50, "SPY": -12 }
GET /v1/account Force a broker account refresh

Refreshes the broker account snapshot, then returns normalized cash, buying power, equity, HTTP status, the raw account body, and timestamps.

Hyperion typically calls this at startup and then on a restrained cadence.

Alias: GET /account

200 / response shape
{
  "ok": true,
  "cash": 125000.00,
  "buying_power": 250000.00,
  "equity": 128420.50,
  "account_http": 200,
  "account": { ... },
  "fetched_ms": 1753891200000
}
GET /v1/positions/refresh Force a full broker position resync

Pulls positions from the active broker target—Alpaca in the current build—replaces local actual quantities, and wakes reconciliation. Use this sparingly for recovery or explicit bootstrap.

On failure, the JSON body contains {"ok":false,"error":"..."}.

Alias: GET /positions/refresh

200 / response shape
{
  "IBM": 50,
  "ok": true,
  "refreshed": true,
  "ts_ms": 1753891200000
}
GET /v1/sources Live producer state and locks

Lists every known source with its stored positions, mute flag, heartbeat age, and computed liveness, plus the current lock object.

Alias: GET /sources

200 / response shape
{
  "ok": true,
  "sources": [{
    "source": "clock_echo",
    "muted": false,
    "positions": { "IBM": 50 },
    "age_sec": 2.4,
    "alive": true
  }],
  "locks": { ... }
}
GET /v1/locks Current trading freezes and mutes

Returns the global block, blocked symbols, muted sources, and lock-behavior flags without the rest of the book.

Alias: GET /locks

200 / response
{
  "ok": true,
  "locks": {
    "global_block": false,
    "blocked_symbols": ["GME"],
    "muted_sources": ["old_bot"],
    "block_freezes_trading_only": true
  }
}

Intent + control

JSON request bodies
POST /v1/intent Inject position intent or a heartbeat

With symbol and position, sets one source’s target. With only source, touches its heartbeat.

If source is omitted it defaults to hyperion. Setting a symbol without a position returns HTTP 400.

Alias: POST /intent

request / intent
curl -sS http://127.0.0.1:8765/v1/intent \
  -H 'Content-Type: application/json' \
  -d '{
    "source": "hyperion",
    "symbol": "IBM",
    "position": 50
  }'
POST /v1/block/symbol Freeze new trading for one symbol

Adds a symbol block and wakes reconciliation. The intended book continues to update, but no new trade is submitted for this symbol.

This does not liquidate. Existing holdings remain unchanged.

Aliases: /block/symbol, /v1/lock/symbol, /lock/symbol

request body
{ "symbol": "IBM" }
DELETE /v1/block/symbol/:symbol Remove one symbol block

Removes the symbol freeze and immediately wakes reconciliation against the current intended book.

Alias: DELETE /v1/lock/symbol/:symbol

example
curl -X DELETE \
  http://127.0.0.1:8765/v1/block/symbol/IBM
POST /v1/unblock/symbol Remove a symbol block by JSON body

Body-form equivalent of the DELETE endpoint. Reconciliation resumes using whatever target accumulated while blocked.

Aliases: /unblock/symbol, /v1/unlock/symbol, /unlock/symbol

request body
{ "symbol": "IBM" }
POST /v1/block/all Freeze all new trading

Sets the global block. Source messages and intended state are still recorded, but all new order placement is frozen.

This does not liquidate. Use the dedicated liquidate endpoint to close positions.

Aliases: /block/all, /v1/lock/all, /lock/all

example
curl -X POST \
  http://127.0.0.1:8765/v1/block/all
POST /v1/unblock/all Clear global and symbol blocks

Clears the global block and every symbol block, then wakes reconciliation. Source mutes are preserved.

The DELETE form is accepted on the block or lock path.

Aliases: POST /v1/unlock/all, DELETE /v1/block/all, DELETE /v1/lock/all, plus unversioned forms

example
curl -X POST \
  http://127.0.0.1:8765/v1/unblock/all

Liquidation + source safety

Operational intervention
POST /v1/liquidate/symbol Close one position and block it

Blocks the symbol, cancels a non-liquidation working order, and uses the active broker target’s close-position operation when quantity is non-zero.

Liquidation always leaves the symbol blocked. If already flat, the result is a block-only action.

Alias: POST /liquidate/symbol

request body
{ "symbol": "IBM" }
POST /v1/liquidate/all Close all positions and block globally

Sets the global block, asks the active broker target to cancel open orders and close all positions, tracks returned liquidation orders, and refreshes actuals.

In dry-run mode it clears local actual and in-flight state without broker networking.

Alias: POST /liquidate/all

example
curl -X POST \
  http://127.0.0.1:8765/v1/liquidate/all
POST /v1/mute/source Remove one source from effective demand

Excludes the source from aggregation and re-reconciles. Its stored positions may continue to update while muted.

Unmuting can therefore restore the source’s latest demand immediately.

Alias: POST /mute/source

request body
{ "source": "clock_echo" }
POST /v1/unmute/source Restore one source to aggregation

Removes the mute and re-reconciles against the source’s currently stored positions.

A path-form DELETE is also available.

Aliases: POST /unmute/source, DELETE /v1/mute/source/:source

request body
{ "source": "clock_echo" }
POST /v1/unlock/all_locks Clear every block and source mute

Clears the global block, all symbol blocks, and all source mutes in one action, then wakes reconciliation.

Use deliberately: this is broader than /v1/unblock/all, which preserves source mutes.

Alias: POST /unlock/all_locks

example
curl -X POST \
  http://127.0.0.1:8765/v1/unlock/all_locks
No endpoint matches this filter. Clear the search or choose another category.

Three channels. One account boundary.

The bus is the live data plane: producers publish intent in, jinghong publishes state out, and observers subscribe. Channel names should carry a unique server suffix on multi-instance hosts.

Important distinction

jinghong is a RESP client, not a Redis server. It connects to goblin-core—or another compatible server—through the configured Unix domain socket.

Inbound

ACCOUNT:ORDER:<server>

One channel per jinghong process. Robots and Hyperion publish position intent and heartbeat JSON here.

jinghong
SUBSCRIBE
producers
PUBLISH
config
JINGHONG_ORDER_CHANNEL
Outbound

ACCOUNT:STATUS:<server>

Lifecycle, book, heartbeat, reconcile, order, fill, lock, and error events for Hyperion and monitors.

jinghong
PUBLISH
consumers
SUBSCRIBE
config
JINGHONG_STATUS_CHANNEL
Audit

ACCOUNT:ALPACA:<server>

Current target-specific channel: a compact summary of Alpaca REST requests for rate, latency, and failure monitoring.

jinghong
PUBLISH
consumers
SUBSCRIBE
config
JINGHONG_ALPACA_CHANNEL

ORDER payload schema

UTF-8 JSON passed as the second argument to RESP PUBLISH.

source string Required stable producer id, unique on this server.
symbol string Ticker for an intent. Omit for a heartbeat.
position number Signed shares. Positive long, negative short, zero clears this source’s symbol.
timing rule Send a heartbeat about every 5s. Default expiry is 15s.
RESP / redis-cli-compatible examples
# Intent: FOO wants +50 IBM
redis-cli -s /run/goblin-core/goblin.sock \
  PUBLISH ACCOUNT:ORDER:paper3 \
  '{"source":"FOO","symbol":"IBM","position":50}'

# Heartbeat: send about every five seconds
redis-cli -s /run/goblin-core/goblin.sock \
  PUBLISH ACCOUNT:ORDER:paper3 \
  '{"source":"FOO"}'

# Explicitly clear FOO's IBM claim
redis-cli -s /run/goblin-core/goblin.sock \
  PUBLISH ACCOUNT:ORDER:paper3 \
  '{"source":"FOO","symbol":"IBM","position":0}'

# Follow live state + current target request audit
redis-cli -s /run/goblin-core/goblin.sock \
  SUBSCRIBE ACCOUNT:STATUS:paper3 ACCOUNT:ALPACA:paper3

STATUS event families

Every status event receives a ts_ms timestamp. The exact fields vary by event; bootstrap with GET /v1/status, then use this stream for live changes.

started heartbeat heartbeat_expire intent intent_error reconcile reconcile_skip reconcile_error order_submit order_cancel order_cancel_no_id order_canceled order_reject cancel_timeout fill_applied dry_fill inflight_poll inflight_poll_error inflight_gone inflight_cleared_at_target liquidator_lock liquidate liquidate_all alpaca_ws

Safe by topology, not by middleware.

The control plane assumes a trusted host. Correct account isolation, channel naming, socket permissions, and bind addresses are part of the deployment contract.

HTTP has no authentication.

Keep JINGHONG_HTTP_BIND=127.0.0.1 unless a protected internal network explicitly needs remote access. The server also returns permissive CORS headers.

Never share an ORDER channel.

One process owns one account book and one inbound channel. Two jinghong processes consuming the same ORDER channel will each act on the same demand.

Block is not liquidate.

A block freezes new orders and leaves positions alone. Liquidation closes through the active broker target and applies a block so stale intent cannot immediately reopen the position.

Watch the heartbeat margin.

Producers should beat about every five seconds against the default fifteen-second expiry. Silence zeros that source’s book and may cause a flattening trade.

01Start goblin-core on the configured UDS.
02Start the account’s jinghong process.
03Confirm the broker target; Alpaca uses REST + trade_updates.
04Start robot heartbeats and intent.
05Attach Hyperion and monitoring.

Put intent on the wire.

jinghong is a focused C++ execution process: one book, one channel, one broker account, and enough control surface to operate it clearly.

View adamdeprince/jinghong →