State the destination.
Producers publish desired signed share counts, not imperative buy and sell commands.
C++ / execution infrastructure
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.
System shape
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.
Publish signed position intents and liveness heartbeats for one account.
JSON payloadsPreferred RESP Pub/Sub transport over a local Unix domain socket.
PUBLISH / SUBSCRIBEAggregates intent, compares actuals, plans deltas, and exposes HTTP control.
C++ processAlpaca ships first: persistent HTTPS for commands and trade_updates WebSocket for order state.
REST + WSSProducers publish desired signed share counts, not imperative buy and sell commands.
Unmuted source positions are summed per symbol into a single account-level book.
Actual holdings are compared with the aggregate target before an order is planned.
Trade updates maintain actual quantity and cash; stale in-flight orders are polled.
Block stops new trades without changing holdings. Liquidate closes and then blocks.
After the heartbeat TTL, a source is zeroed and the aggregate book is reconciled again.
Execution model
jinghong deliberately keeps its execution vocabulary small. The process accepts target state, emits market orders, and makes cancellation, direction flips, and operator intervention explicit.
Sum every unmuted producer on this process’s ORDER channel. A zero position clears only that source’s symbol claim.
Use bootstrapped positions plus applied fill events to determine the smallest market-order delta.
Global or symbol blocks suppress new orders while the intended book continues to update for inspection.
If aggregate demand changes during an open order, request cancellation and re-plan one order against the new net target.
A long-to-short or short-to-long transition first flattens, then opens the opposite side after the terminal event.
Lifecycle, intent, reconcile, order, fill, heartbeat, lock, and error events are published for Hyperion and monitors.
Goblin stack
Each project has one job. Their shared channel conventions and simple interfaces let a desk UI, strategy robots, and execution process move independently without inventing another control protocol.
Runs the familiar RESP Pub/Sub commands jinghong needs over a local Unix socket. This is the first-choice deployment.
Visit goblin-core.dev → Desk UIBootstraps the book over HTTP, follows STATUS and the current ACCOUNT:ALPACA audit stream, and may publish intent as source hyperion.
Publishes trade and quote marks for consumers such as Hyperion. jinghong itself does not consume market data.
Open goblin-slurp →Transport choice
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.
Installation
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.
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.
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
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.
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)"
First run
This example starts the paper3 process on HTTP port 8768.
Use one unique channel pair for every jinghong instance.
# 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.
HTTP control plane
Read endpoints return local book state unless their name explicitly says refresh. Command endpoints mutate intent or safety controls and trigger reconciliation as needed.
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.
/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
{
"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
}
/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 /
{ "IBM": 50, "SPY": -12 }
/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
{
"ok": true,
"cash": 125000.00,
"buying_power": 250000.00,
"equity": 128420.50,
"account_http": 200,
"account": { ... },
"fetched_ms": 1753891200000
}
/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
{
"IBM": 50,
"ok": true,
"refreshed": true,
"ts_ms": 1753891200000
}
/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
{
"ok": true,
"sources": [{
"source": "clock_echo",
"muted": false,
"positions": { "IBM": 50 },
"age_sec": 2.4,
"alive": true
}],
"locks": { ... }
}
/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
{
"ok": true,
"locks": {
"global_block": false,
"blocked_symbols": ["GME"],
"muted_sources": ["old_bot"],
"block_freezes_trading_only": true
}
}
/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
curl -sS http://127.0.0.1:8765/v1/intent \
-H 'Content-Type: application/json' \
-d '{
"source": "hyperion",
"symbol": "IBM",
"position": 50
}'
/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
{ "symbol": "IBM" }
/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
curl -X DELETE \ http://127.0.0.1:8765/v1/block/symbol/IBM
/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
{ "symbol": "IBM" }
/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
curl -X POST \ http://127.0.0.1:8765/v1/block/all
/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
curl -X POST \ http://127.0.0.1:8765/v1/unblock/all
/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
{ "symbol": "IBM" }
/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
curl -X POST \ http://127.0.0.1:8765/v1/liquidate/all
/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
{ "source": "clock_echo" }
/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
{ "source": "clock_echo" }
/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
curl -X POST \ http://127.0.0.1:8765/v1/unlock/all_locks
RESP Pub/Sub endpoint
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.
jinghong is a RESP client, not a Redis server. It connects to goblin-core—or another compatible server—through the configured Unix domain socket.
One channel per jinghong process. Robots and Hyperion publish position intent and heartbeat JSON here.
Lifecycle, book, heartbeat, reconcile, order, fill, lock, and error events for Hyperion and monitors.
Current target-specific channel: a compact summary of Alpaca REST requests for rate, latency, and failure monitoring.
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.
# 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
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.
Operator notes
The control plane assumes a trusted host. Correct account isolation, channel naming, socket permissions, and bind addresses are part of the deployment contract.
Keep JINGHONG_HTTP_BIND=127.0.0.1 unless a protected internal network explicitly needs remote access. The server also returns permissive CORS headers.
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.
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.
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.
Source available
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 →