Hey-LiSA is in development phase and pre-alpha. These docs are still in progress and not complete yet.

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

ActionRequired fieldsPurpose
cashflowamountAdds simulated quote cash.
buyasset, price, sizingSimulates a BTC spot buy.
sellasset, price, sizingSimulates a BTC spot sell from the current BTC holding.
setvaluesUpdates 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 typeAmountMeaning
base_amountPositive BTC amount or expressionSells up to the requested BTC amount.
percent_positionMore than 0 and at most 100Sells that percentage of the current BTC position.
all_positionOmittedSells 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

ActionRequired fieldsPurpose
open_positionlifecycle_description, side, sizing, protection.stop_loss; optional take profit, targets, stop updates, and inline exit_conditionsOpens one BTC long or short position when next-open settlement leaves the account flat.
close_positionNone beyond name and typeCloses 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",
  "marker_template": "Open Long ({entry_notional})",
  "lifecycle_description": "Open at 1x with a 5% target and a 2% stop.",
  "side": "long",
  "sizing": {
    "type": "percent_equity",
    "amount": 100
  },
  "leverage": 1,
  "protection": {
    "take_profit": {
      "type": "percent_from_entry",
      "amount": 5,
      "marker_template": "Exit (TP hit) ({event_pnl})"
    },
    "stop_loss": {
      "type": "percent_from_entry",
      "amount": 2,
      "marker_template": "Exit (SL hit) ({event_pnl})"
    }
  }
}

lifecycle_description is required on every Trading opening. It is exact, strategy-authored plain text from 1 through 240 characters, including up to three ASCII line feeds. It may contain from one through four nonempty lines (\n in JSON source), with no leading or trailing whitespace on any line. A fourth line feed, carriage return, tab, Unicode line or paragraph separator, control character, or formatting character is invalid. Hey-LiSA passes accepted text through exactly without parsing, rewriting, or interpreting Markdown or HTML. The Decision tree preserves it before the backend-generated executable lifecycle mechanics. It is not accepted on close_position.

side must be "long" or "short". Sizing supports:

Sizing typeAmountMeaning
percent_equityMore than 0 and at most 100Opens entry notional equal to that percentage of pre-entry account equity at the explicitly authored leverage: 1.
risk_percent_equity_at_stopMore than 0 and at most 100Sizes 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.

At the next genuine open, active protection and authored exits for the existing position settle first. The complete opening condition tree is then evaluated once: market reads use the completed signal candle and position_state() uses the updated position state. A selected opening executes only if no position remains; a partial position makes it ineligible. 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",
      "marker_template": "Exit (SL hit) ({event_pnl})"
    },
    "targets": [
      {
        "name": "TP1",
        "marker_template": "TP1 ({event_pnl})",
        "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()",
            "marker_template": "Exit (Break-even SL hit) ({event_pnl})"
          }
        }
      }
    ],
    "stop_updates": [
      {
        "name": "Trail after two entry ATR",
        "marker_template": "Exit (Trailing SL hit) ({event_pnl})",
        "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",
    "marker_template": "Exit (Bearish Supertrend) ({event_pnl})",
    "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",
  "marker_template": "Exit (Rule) ({event_pnl})"
}

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. An authored close and an independent opening rule may use the same completed candle. At the next genuine open, the close settles first and the complete opening tree then reads the resulting account state; this is two ordered actions, not an automatic flip.

Trading chart marker templates

marker_template is optional presentation text for a chartable Trading event. It is authored directly on an open_position or close_position action, an initial take-profit or stop-loss level, a target object, an after_fill.stop_loss level, a stop-update object, or an exit-condition group. The field belongs to the target or stop-update instruction, not its nested level definition.

Templates must contain 1 through 64 printable characters on one line, with no leading or trailing whitespace. Control and formatting characters and Unicode line or paragraph separators are rejected. Braces may contain only a supported placeholder. A supported placeholder may appear at most once, and the rendered text must also fit within 64 characters.

{entry_notional} is valid only on open_position; it renders the execution’s positive USD notional in compact form, for example Open Long (14.3K).

{event_pnl} is valid only on chartable exit instructions: close_position, take-profit and stop-loss protection, managed targets and stop changes, and exit-condition groups. It resolves to the net contribution of that exact quantity reduction: gross realized PnL less its exit fee and its deterministic share of the position’s entry fee. A partial target is local to that fill; a later final marker does not include earlier reductions. Entry fee is allocated by exited quantity, with the final exit receiving the raw accounting residual so exact contributions reconcile to the completed trade’s net PnL. Display values use chronological cumulative rounding: cumulative exact contribution is rounded half-up to a whole dollar after each exit, and each marker receives the change from the previous displayed cumulative amount. Earlier partial markers remain stable, and a complete set of displayed event values adds to the completed trade’s whole-dollar net PnL without forcing a false final sign. Values always use an explicit sign followed by the dollar symbol, such as +127$, -80$, or +0$, and are not compacted to K or M. If an exit omits {event_pnl}, that contribution remains absent from the chart and the visible subset is not a complete trade total. Exact raw PnL, fees, entry-fee allocation, and display-rounding evidence remain in the backend report payload.

The chart uses the rendered authored text exactly. The backend does not add a prefix or suffix, infer wording from event type, or fall back to name. If a chartable instruction omits marker_template, its event has no chart marker; the execution, position lifecycle, and audit evidence remain available in the rest of the report.

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.