SharpeBench agents are external and language-agnostic — a container or an HTTP endpoint, in any language. The harness drives an agent through the same two-message loop every decision step:
- harness → agent: a
MarketObservation(JSON), point-in-time. - agent → harness: a
Decision(JSON).
src/main.rs here is the minimal honest implementation in Rust (equal-weight
buy-and-hold) using the typed sharpebench-protocol. Fork it, replace decide,
ship it. Any other language just matches the JSON shapes below.
The harness supports two transports; the JSON payloads are identical across both.
One MarketObservation JSON object per line on stdin; one Decision JSON
object per line on stdout, flushed each line (the loop is line-synchronous).
Driven by sharpebench_sim::ExternalAgent::spawn(program, args).
cargo run -p reference-agent # run it directly
# or containerize it (build from the repo root — it uses the workspace crate):
docker build -f examples/reference-agent/Dockerfile -t sharpebench-reference-agent .
docker run -i --rm sharpebench-reference-agentA plain-HTTP endpoint that accepts POST /decide with a MarketObservation
body and returns a Decision body. Driven by sharpebench_sim::HttpAgent::new("host:port")
(loopback / in-sandbox; no TLS). Pseudocode:
POST /decide HTTP/1.1
Content-Type: application/json
{ ...MarketObservation... } -> 200 OK { ...Decision... }
On any connection or parse error, both transports degrade to a hold (empty orders) — they never crash the harness.
{
"date": "2025-01-02",
"cash": 1.0,
"symbols": [
{
"symbol": "AAPL",
"close_history": [187.2, 188.0, 190.4],
"fundamentals": { "pe": 28.1 },
"news": ["Apple unveils ..."]
}
],
"portfolio": [
{ "symbol": "AAPL", "shares": 3.0, "avg_price": 188.0 }
]
}close_historyis oldest-first and point-in-time: it only contains closes at or beforedate.fundamentalsandnewsfollow the same rule. Look-ahead is impossible by construction — the harness never sends future rows.
{
"orders": [
{ "symbol": "AAPL", "action": "buy", "target_weight": 0.5, "confidence": 0.7 }
],
"reasoning": "optional free text, captured into the trajectory"
}action∈"buy" | "sell" | "hold" | "close"(lower-case).target_weightis the desired portfolio weight for the symbol in[0, 1](signed for shorts); sizing is carried here, not byaction.confidence∈[0, 1](defaults to0.5) is your stated conviction — it is scored for calibration (Brier), so report it honestly: claiming 0.9 on coin-flips is penalized.reasoningis optional and captured for auditability.
Omitted symbols are left untouched. A Decision with no orders is a valid hold.