This document defines all JSON data structures used in both Tier 1 (WASM) and Tier 2 (process) strategies. The schemas are identical across tiers -- the only difference is the transport mechanism (linear memory vs. stdin/stdout).
The manifest declares a strategy's identity, version, and data requirements. For WASM strategies, it is returned by springbot_manifest(). For process strategies, it is sent as a response to the {"type": "manifest"} message.
{
"id": "bollinger-bands",
"name": "Bollinger Bands",
"version": "1.2.0",
"tier": "wasm",
"author": "springbot",
"description": "Mean-reversion strategy using Bollinger Bands",
"requires": {
"candles": { "timeframe": "1h", "lookback": 20 },
"ticker": true
}
}| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique slug identifier. Used as the strategy's key throughout the system. Examples: "bollinger-bands", "ob-imbalance". |
name |
string |
Yes | Human-readable display name. Examples: "Bollinger Bands", "Order Book Imbalance". |
version |
string |
Yes | Semver version string. Examples: "1.0.0", "2.1.3". |
tier |
string |
No | "wasm" or "process". Defaults to "wasm" if omitted. For process strategies, the engine forces this to "process" regardless of what the strategy reports. |
author |
string |
No | Strategy author name. |
description |
string |
No | Human-readable description. |
requires |
object |
Yes | Data requirements (see Requires below). Determines what market data the engine delivers on each tick. |
The requires object declares what market data the strategy needs. The engine uses this to:
- Subscribe to the correct data feeds (WebSocket channels, REST pollers)
- Slice the market data snapshot to include only the requested fields
- Compute the union of requirements when multiple strategies run on the same bot
Only the fields you declare are delivered to your strategy on each tick. Unrequested fields are omitted entirely from the EvaluateInput JSON.
{
"candles": { "timeframe": "1h", "lookback": 20 },
"ticker": true,
"order_book": { "depth": 10 },
"trades": { "count": 50 },
"funding_rate": true,
"open_interest": true,
"portfolio": true
}| Field | Type | Description |
|---|---|---|
candles |
object |
OHLCV candlestick data. See CandleRequirement. |
ticker |
bool |
Real-time price ticker (best bid, best ask, last price, 24h volume/change). |
order_book |
object |
Current order book snapshot. See OrderBookRequirement. |
trades |
object |
Recent executed trades from the exchange. See TradeRequirement. |
funding_rate |
bool |
Current and predicted funding rate (perpetual futures only). |
open_interest |
bool |
Current open interest and change (perpetual futures only). |
portfolio |
bool |
The bot's current base and quote balances plus open orders. |
All fields are optional. A strategy must declare at least one requirement.
{ "timeframe": "1h", "lookback": 20 }| Field | Type | Required | Description |
|---|---|---|---|
timeframe |
string |
Yes | Candle interval. Valid values: "1m", "5m", "15m", "1h", "4h", "1d". |
lookback |
int |
Yes | Number of candles needed. Must be greater than 0. The engine maintains a ring buffer of this size and delivers up to lookback candles on each tick. On early ticks, fewer candles may be available. |
{ "depth": 10 }| Field | Type | Required | Description |
|---|---|---|---|
depth |
int |
Yes | Number of price levels on each side (bids and asks). Must be greater than 0. The engine truncates the order book to this depth. |
{ "count": 50 }| Field | Type | Required | Description |
|---|---|---|---|
count |
int |
Yes | Number of recent trades to include. Must be greater than 0. The engine maintains a ring buffer of recent trades and delivers up to count trades on each tick. |
The EvaluateInput is the complete data payload delivered to a strategy on each tick. It contains only the fields the strategy declared in its manifest requires -- all other fields are omitted from the JSON (not null, not empty arrays, fully absent).
{
"candles": [
{
"time": 1714435200,
"open": 62150.00,
"high": 62480.50,
"low": 62020.00,
"close": 62350.25,
"volume": 1842.5
}
],
"ticker": {
"best_bid": 62340.00,
"best_ask": 62355.50,
"last_price": 62350.25,
"volume_24h": 45230.8,
"change_24h": 1.25
},
"order_book": {
"bids": [
{ "price": 62340.00, "size": 1.5 },
{ "price": 62335.00, "size": 3.2 }
],
"asks": [
{ "price": 62355.50, "size": 0.8 },
{ "price": 62360.00, "size": 2.1 }
]
},
"trades": [
{
"time": 1714438800,
"price": 62350.25,
"size": 0.15,
"side": "buy"
}
],
"funding_rate": {
"current": 0.0001,
"predicted": 0.00012,
"next_funding": 1714449600
},
"open_interest": {
"value": 28500000000.0,
"change": 2.5
},
"portfolio": {
"base_balance": 0.5,
"quote_balance": 10000.00,
"open_orders": [
{
"id": "ord-abc123",
"side": "buy",
"price": 62100.00,
"size": 0.1
}
]
}
}Array of OHLCV candlestick objects, ordered chronologically (oldest first, most recent last). Present only if requires.candles is declared.
| Field | Type | Description |
|---|---|---|
time |
int |
Unix timestamp in seconds (UTC). Start time of the candle. |
open |
float |
Opening price. |
high |
float |
Highest price during the interval. |
low |
float |
Lowest price during the interval. |
close |
float |
Closing price. |
volume |
float |
Trading volume during the interval (in base asset units). |
Note: On early ticks after a bot starts, the array may contain fewer candles than the declared lookback. Always check the array length before accessing elements.
Real-time price information. Present only if requires.ticker is true.
| Field | Type | Description |
|---|---|---|
best_bid |
float |
Highest current bid price. |
best_ask |
float |
Lowest current ask price. |
last_price |
float |
Last traded price. |
volume_24h |
float |
Total volume over the last 24 hours (in base asset units). |
change_24h |
float |
Price change over the last 24 hours as a percentage. |
Current state of the order book, truncated to the requested depth. Present only if requires.order_book is declared.
| Field | Type | Description |
|---|---|---|
bids |
array |
Bid price levels, sorted best first (highest bid at index 0). |
asks |
array |
Ask price levels, sorted best first (lowest ask at index 0). |
Each price level:
| Field | Type | Description |
|---|---|---|
price |
float |
Price at this level. |
size |
float |
Total size at this level (in base asset units). |
Array of recent executed trades from the exchange, ordered chronologically (oldest first). Present only if requires.trades is declared.
| Field | Type | Description |
|---|---|---|
time |
int |
Unix timestamp in seconds (UTC). |
price |
float |
Execution price. |
size |
float |
Trade size (in base asset units). |
side |
string |
"buy" or "sell" (lowercase). |
Current and predicted funding rate for perpetual futures contracts. Present only if requires.funding_rate is true. Not all exchanges or trading pairs support this.
| Field | Type | Description |
|---|---|---|
current |
float |
Current funding rate (e.g., 0.0001 = 0.01%). |
predicted |
float |
Predicted next funding rate. |
next_funding |
int |
Unix timestamp in seconds of the next funding event. |
Current open interest for perpetual futures. Present only if requires.open_interest is true. Not all exchanges or trading pairs support this.
| Field | Type | Description |
|---|---|---|
value |
float |
Total open interest (in quote currency). |
change |
float |
Change in open interest as a percentage. |
The bot's current position and balances. Present only if requires.portfolio is true. This is per-bot data, not account-wide.
| Field | Type | Description |
|---|---|---|
quote_balance |
float |
Available quote currency balance (e.g., USD). |
base_balance |
float |
Available base asset balance (e.g., BTC). |
open_orders |
array |
List of currently open orders for this bot. |
Each open order:
| Field | Type | Description |
|---|---|---|
id |
string |
Exchange order ID. |
side |
string |
"buy" or "sell" (lowercase). |
price |
float |
Order price. |
size |
float |
Order size (in base asset units). |
The signal is the output of a strategy evaluation. It tells the engine what action the strategy recommends and how confident it is.
{
"direction": "BUY",
"confidence": 0.85
}| Field | Type | Required | Description |
|---|---|---|---|
direction |
string |
Yes | Trading action: "BUY", "SELL", or "HOLD". Case-sensitive. |
confidence |
float |
Yes | Confidence level from 0.0 to 1.0 (inclusive). |
| Direction | Meaning |
|---|---|
"BUY" |
The strategy recommends buying the base asset. |
"SELL" |
The strategy recommends selling the base asset. |
"HOLD" |
The strategy recommends no action. The engine will never place a trade on HOLD. |
Confidence is used in two ways by the engine:
1. Weighted signal aggregation
When a bot runs multiple strategies, their signals are combined using weighted aggregation. Each strategy has a configured weight (set when creating a bot config). The aggregation formula is:
score = sum(weight_i * confidence_i * direction_value_i) / sum(weight_i)
Where direction_value maps BUY to +1, SELL to -1, and HOLD to 0.
The resulting score is a normalized value in the range [-1.0, +1.0]:
- If
score > buy_threshold-> the engine places a BUY order - If
score < -sell_threshold-> the engine places a SELL order - Otherwise -> HOLD (no trade)
The thresholds are configured per bot config (typical defaults: buy_threshold = 0.5, sell_threshold = 0.5).
Example: A bot runs two strategies with equal weight (1.0 each):
- Strategy A:
BUYwith confidence0.9 - Strategy B:
SELLwith confidence0.3 - Score:
(1.0 * 0.9 * 1 + 1.0 * 0.3 * -1) / (1.0 + 1.0)=0.6 / 2.0=0.3 - With a buy threshold of
0.5, this results inHOLD(0.3 < 0.5)
2. Position sizing
After aggregation produces a BUY or SELL action, the absolute value of the aggregate score is used as a confidence factor for position sizing. Higher aggregate confidence results in larger order sizes relative to the bot's available capital.
A signal is invalid if:
directionis not one of"BUY","SELL","HOLD"(case-sensitive, uppercase only)confidenceis less than0.0or greater than1.0- Either field is missing
Invalid signals are rejected by the engine. The supervision layer treats an invalid signal as a strategy failure, logs it, and returns HOLD with confidence 0.0 for that tick.