Actions and execution
Actions are the operations a strategy asks the backtest engine to simulate after its conditions resolve. Every action needs a readable name and a supported type. Authored action IDs are not accepted.
DCA actions
| Action | Required fields | Purpose |
|---|---|---|
cashflow | amount | Adds simulated quote cash. |
buy | asset, price, sizing | Simulates a BTC spot buy. |
sell | asset, price, sizing | Simulates a BTC spot sell from the current BTC holding. |
set | values | Updates strategy-owned variables. |
Buy sizing
{
"name": "Deploy reserve cash",
"type": "buy",
"asset": "BTC",
"price": "price()",
"sizing": {
"type": "quote_amount",
"amount": "get('cash') * 0.25"
}
} Buy sizing supports quote_amount. The amount can be a positive number or supported expression. max_total_spend is optional and must be positive when present.
Buy fills are cash-limited. If the requested quote amount exceeds available cash or the remaining cap, the simulated fill is partial or zero and cash never goes negative.
Sell sizing
Spot sells reduce existing BTC and cannot create a short position.
| Sizing type | Amount | Meaning |
|---|---|---|
base_amount | Positive BTC amount or expression | Sells up to the requested BTC amount. |
percent_position | More than 0 and at most 100 | Sells that percentage of the current BTC position. |
all_position | Omitted | Sells the complete current BTC position. |
{
"name": "Sell all BTC",
"type": "sell",
"asset": "BTC",
"price": "price()",
"sizing": {
"type": "all_position"
}
} Sell fills are capped by the current BTC holding. Sale proceeds remain simulated quote cash.
DCA portfolio and execution
{
"portfolio": { "quote_asset": "USDC", "starting_cash": 0 },
"execution": { "price": "next_candle_open", "fee_bps": 5, "slippage_bps": 5 }
} portfolio.quote_asset must be "USDC" and starting_cash must be non-negative. DCA execution supports "next_candle_open" and "candle_close". Fees and slippage must be non-negative.
Trading actions
| Action | Required fields | Purpose |
|---|---|---|
open_position | side, sizing, protection.stop_loss; optional take profit, targets, stop updates, and inline exit_conditions | Opens one BTC long or short position from flat. |
close_position | None beyond name and type | Closes the complete current position. |
The current Trading contract supports one position at a time. It cannot add to, reverse, or automatically flip a position. Managed targets can reduce the original quantity in stages inside the same trade lifecycle.
Open a position
{
"name": "Open long",
"type": "open_position",
"side": "long",
"sizing": {
"type": "percent_equity",
"amount": 100
},
"leverage": 1,
"protection": {
"take_profit": {
"type": "percent_from_entry",
"amount": 5
},
"stop_loss": {
"type": "percent_from_entry",
"amount": 2
}
}
} side must be "long" or "short". Sizing supports:
| Sizing type | Amount | Meaning |
|---|---|---|
percent_equity | More than 0 and at most 100 | Opens entry notional equal to that percentage of pre-entry account equity at the explicitly authored leverage: 1. |
risk_percent_equity_at_stop | More than 0 and at most 100 | Sizes BTC so planned price loss at the resolved stop equals that percentage of pre-entry account equity. |
Stop-risk sizing uses the actual next-open entry and frozen stop-loss level. For pre-entry equity E, selected risk fraction r, and adverse entry-to-stop distance d, base size is E * r / d. Entry and exit fees are excluded from that target, so net loss at the stop can exceed the selected percentage. This mode derives and records effective leverage and does not accept an authored leverage field. Equity-percentage sizing retains its explicit 1x field.
An open rule must include a position_state() == "flat" condition. Opening while a position is already open is rejected. Every open must configure an initial stop loss. The take-profit shorthand is optional.
Position protection
protection.stop_loss is required and protection.take_profit is optional. A fixed
percentage uses type: "percent_from_entry". A price distance uses type: "price_distance_from_entry" and accepts a positive number or supported
numeric expression.
An expression-based distance can read price, any declared indicator, and
their historical genuine-candle offsets. It is evaluated once on the completed
signal candle and stored with the pending open action. The absolute take-profit
and stop-loss prices are then derived from the actual next-open entry. Managed
positions may also snapshot named entry_values, close percentages of initial
quantity through targets, change the stop after a confirmed target fill, and
apply one-time or recurring completed-candle stop_updates. Stop updates are
tighten-only.
"protection": {
"take_profit": {
"type": "price_distance_from_entry",
"amount": "atr(14)"
},
"stop_loss": {
"type": "price_distance_from_entry",
"amount": "atr(14) * 1.5"
}
} Complete managed protection
With ATR 14 declared in the strategy’s indicators, this complete protection value snapshots ATR on the signal candle, takes a
partial target, moves the shared stop after that target fills, and then applies
a recurring tighten-only stop update:
{
"protection": {
"entry_values": {
"entry_atr": "atr(14)"
},
"stop_loss": {
"type": "price_distance_from_entry",
"amount": "entry_value('entry_atr') * 1.5"
},
"targets": [
{
"name": "TP1",
"level": {
"type": "price_distance_from_entry",
"amount": "entry_value('entry_atr')"
},
"quantity": {
"type": "percent_initial",
"amount": 50
},
"after_fill": {
"stop_loss": {
"type": "absolute_price",
"amount": "position_entry_price()"
}
}
}
],
"stop_updates": [
{
"name": "Trail after two entry ATR",
"cadence": "each_completed_candle",
"conditions": [
{
"name": "Price advanced two entry ATR",
"left": "price()",
"op": ">=",
"right": "position_entry_price() + entry_value('entry_atr') * 2"
}
],
"stop_loss": {
"type": "absolute_price",
"amount": "highest_since_entry('high') - entry_value('entry_atr') * 1.5"
}
}
]
}
} Protection is established from the executed entry and remains active until it is replaced or the position closes. Targets use percentages of initial executed quantity. The shared stop always closes 100% of current remaining quantity. Daily and 4h runs use the canonical Kraken 1-minute history to determine which active trigger is reached first. A genuine minute open beyond a level executes at that exact open. Otherwise, the exit executes at the exact trigger level. If the same 1-minute candle crosses an active stop and target, the stop loss is selected conservatively. Fees apply separately and no adverse slippage adjustment is added.
Target executions, stop activations, and intended reductions are recorded explicitly. A target-driven stop change begins at the next genuine minute after the target executes; a completed-candle update begins at the first genuine minute after that close. The backtest applies intended reductions atomically and does not model order-book liquidity.
entry_values can read completed-candle prices and indicators, but not an open
position. Initial stop and target expressions can additionally read a declared entry_value(name). After-fill stop changes, stop updates, and position-bound
exit conditions may read position_entry_price(), position_age_candles(), initial_stop_distance(), highest_since_entry('high'), lowest_since_entry('low'), and declared entry
values while their position is open. Target quantities use { "type": "percent_initial", "amount": ... }. A target may define only a
stop change in after_fill; unrestricted management actions are not supported.
Entry-value names use exact lowercase snake case and must begin with a letter.
The declaration and entry_value('name') reference must match exactly;
whitespace and aliases are rejected rather than normalized. Favorable-extrema
reads are also exact: highest_since_entry('high') and lowest_since_entry('low') are the supported forms.
Position-bound exit conditions
An open_position can include ordered, named exit condition groups directly in
that action. Conditions within a group must all pass. Groups are alternatives
checked in authored order. The definitions bind to the position opened by that
action; there are no reusable exit-policy definitions or references.
"exit_conditions": [
{
"name": "Exit long while Supertrend is bearish",
"conditions": [
{
"name": "Supertrend is bearish",
"left": "supertrend_bearish(10, 3)",
"op": "==",
"right": 1
}
]
}
] Bound conditions are evaluated on completed genuine candles while their
position is open, beginning with the entry candle. The first matching group
schedules a full close at the next genuine candle open. A selected top-level close_position rule takes precedence. Take-profit or stop-loss execution on
the signal candle closes the position before bound conditions are considered;
a genuine next open beyond protection supersedes a pending condition exit.
Bound exits do not reverse or open an opposite position.
Close a position
{
"name": "Close long",
"type": "close_position"
} A close rule must require position_state() to be "long" or "short". A close action has no side, sizing, or protection fields. It remains available for indicator-based and other conditional full-position exits, even though every position also has take profit and stop loss. Changing direction requires a close signal and a later open signal.
Trading account and execution
{
"account": { "collateral_asset": "USDC", "starting_balance": 10000 },
"execution": { "price": "next_candle_open", "fee_bps": 4.5 }
} The starting balance must be positive. For both 1d and 4h strategies, Trading signals are evaluated after a completed strategy-timeframe candle closes and execute at the exact next tradable candle open for the same timeframe. Trading requires the fixed base-tier taker assumption of 4.5 bps on entry notional and 4.5 bps on exit notional. It does not accept a slippage setting. Discounts, rebates, referrals, builder fees, funding, collateral constraints, maintenance margin, and liquidation are not simulated. A Kraken 4h interval with no trades is represented internally as a nontradable previous-close slot for indicator timing. Rules and fills skip it, so a pending action waits for the next genuine candle open.
Constraints
DCA constraints support start and end timestamps plus stop_when_budget_spent. Trading constraints currently support start and end timestamps only. An end timestamp must be after the start timestamp.
Marker metadata
DCA buy and sell actions may include chart marker metadata:
{ "marker": { "color": "#33b074" } } The color must be #RGB or #RRGGBB. Marker metadata affects chart presentation only.