# Tiny Autonomous Live Runtime Runner Pattern

Use this reference when a paper/testnet-proven Hyperliquid strategy is ready for a **minimal, risk-bounded autonomous Mainnet runner**. The goal is not new research or a large platform; it is the smallest safe loop that can place real orders only after all gates are green.

## Preconditions

Before live mode exists or starts, require:

- Dedicated final preflight mode returns `PASS` or `PASS_WITH_CONDITIONAL_SCHEDULECANCEL` only.
- Mainnet signed credential model is validated:
  - funded account/subaccount address is present;
  - API/agent wallet address is separate from account;
  - private key derived address equals API/agent wallet address;
  - signed Mainnet validation is only allowed inside the final live preflight / live runner path.
- Mainnet read-only reconcile is clean before start: no unexpected positions or orders.
- Testnet strategy smoke has proven forced `OrderIntent` → fill → reduceOnly stop → stop reconcile → cleanup.
- Real alert smoke has been delivered to the intended Telegram chat/topic, including daily-report and critical-alert shapes.
- Daily/weekly/loss-streak/kill-switch/reconcile gates are explicit config, not comments.

## Runner shape

Build one CLI entrypoint, for example:

```bash
.venv/bin/python -m src.tools.tiny_autonomous_live_runtime --mode shadow --iterations 5 --interval-seconds 2 --json
CTB_TINY_AUTONOMOUS_LIVE_ALLOWED=true \
  .venv/bin/python -m src.tools.tiny_autonomous_live_runtime --mode live --iterations 1440 --interval-seconds 60 --json
```

Key properties:

1. **Shadow by default.** The default mode uses real Mainnet market data and real read-only reconcile, creates/journals intents, but never signs or sends orders.
2. **Live requires an explicit env gate.** `--mode live` must fail closed unless a clear variable such as `CTB_TINY_AUTONOMOUS_LIVE_ALLOWED=true` is set.
3. **Strategy remains pure.** The strategy returns `OrderIntent`; it must not import exchange SDKs or send orders.
4. **Runtime owns side effects.** Only the runner/executor may set leverage, place entry orders, place stops, or cancel/repair orders.
5. **Persistent state.** Write runtime state and JSONL journal under `runtime/live/<runner>/` or user-local state. Track open entries, client IDs, sizes, stop loss, previous mids, recent trade coins, loss counters, and restart/crash metadata.
6. **Persistent nonce/signing guard.** Use a persistent nonce manager and signer lock for live mode. Do not let shadow mode acquire a live signer lock; stale shadow locks can block live startup.
7. **Critical alerts only.** Normal mini-trades go to journal/daily report. Immediate chat alerts are for stop missing, kill-switch, loss limit, unexpected exposure, reconcile mismatch, unrecoverable process/API failure.

## Trading-loop gates

Each iteration should fail closed before scanning signals if any of these are true:

- preflight is not `PASS`/`PASS_WITH_CONDITIONAL_SCHEDULECANCEL`;
- kill-switch file active;
- daily loss <= configured negative limit;
- weekly loss <= configured negative limit;
- consecutive losses >= configured max;
- API health degraded or market data stale;
- read-only reconcile is unsafe;
- open positions exceed limit;
- total open notional exceeds limit;
- any position lacks a confirmed reduceOnly stop.

For every blocked signal, journal a compact reason (`no_v76_intent`, `risk_gate`, `data_quality`, `spread`, `wld_leakage_gate`, etc.) instead of silently skipping.

### Running-session preflight nuance

The initial **start** preflight should require `no_open_positions_or_orders_before_start`. Once the runtime is already managing live exposure, do **not** let that start-only gate deadlock the loop. A running session may continue if the only failed preflight gate is `no_open_positions_or_orders_before_start` **and** the runtime's own reconcile confirms:

- every exchange position is represented in live runtime state;
- every position has a matching exchange-level reduceOnly stop;
- stop side and size match the long position;
- open position count and total notional remain within limits;
- `block_new_entries=false` and `stops_missing_count=0`.

Prune stale local `open_entries` for coins that no longer exist on exchange before running this check, then save state. This prevents old closed trades from causing false reconcile warnings while still blocking on real unknown exposure.

## Entry and stop sequence

1. Scan only allowed coins.
2. Enforce long-only, no martingale, no averaging down, no blind dip buying.
3. Build a v76-compatible `OrderIntent` with stop loss.
4. Risk gate checks notional, risk USD, open positions, stop presence, kill-switch/loss state.
5. In live mode:
   - set leverage to 1x/allowed cap;
   - place entry using bounded IOC/market-like behavior;
   - poll/read reconcile for fill;
   - immediately place exchange-level reduceOnly stop for the filled size;
   - reconcile stop side/size/reduceOnly before permitting any later entries.
6. If stop confirmation fails, set `block_new_entries=true`, send critical alert, and either run a narrowly scoped repair/exit path or block cleanly. Do not continue scanning new entries.

## Profit protection / exit management

The live runner must not be only a downside stop machine. Once positions are open, each loop should manage exits before scanning new entries:

1. Read current positions, open reduceOnly stops, and current mids.
2. For each long position, preserve the original stop as `initial_stop_loss` so R-multiple calculations stay stable even after the stop is tightened.
3. Compute `pnl_r = (current_mid - entry_px) / (entry_px - initial_stop_loss)`.
4. Tighten stops only upward; never loosen a long stop.
5. Suggested tiers for small-account v76 live-candidate mode:
   - `break_even_after_r ~= 0.75`: move stop to entry plus a tiny cushion;
   - `profit_lock_after_r ~= 1.25`: lock a fraction of open profit;
   - `trailing_after_r ~= 1.8`: trail below current price using a fraction/multiple of the initial risk distance;
   - keep the stop safely below current price to avoid immediate self-trigger from noise.
6. For Hyperliquid stop upgrades, submit the tighter new reduceOnly trigger stop first and only then cancel older looser stops for that coin. If the new stop is rejected, keep the old stop and block/journal the rejection rather than removing protection.
7. After stop upgrades, refresh open orders and run reconcile again; `stops_missing_count` must remain zero.
8. Use dead-fish/time exits for positions that consume risk but fail to progress after the configured window; this should be a controlled reduceOnly exit, not a new strategy.

Journal compact events such as `exit_management_stop_upgraded` with coin, old stop, new stop, reason (`break_even_plus_cushion`, `profit_lock`, `trailing_stop`), `pnl_r`, and whether a Mainnet signed action occurred.

## Reconcile integration pitfall

After the first live trade, a generic read-only reconcile that has `local_orders=[]` may falsely report `exchange_position_without_local_journal`. Fix the generic reconciler/watchdog to load the live runtime state (`open_entries`) as local order records before comparing exchange exposure. Verify that the watchdog becomes green with:

- one exchange position;
- one matching reduceOnly stop;
- matching local entry state;
- `stops_missing_count=0`;
- `block_new_entries=false`.

This is a durable integration rule: live runtime state and external watchdog state must share the same local-order source of truth.

## Verification before live start

Run, in order:

1. focused unit tests for credential/reconcile/runtime gate behavior;
2. full `pytest`;
3. `compileall src tests`;
4. shadow runtime for several iterations with real Mainnet market data/reconcile and `mainnet_signed_action=false`;
5. final preflight;
6. start live only if all gates are green except allowed conditional `scheduleCancel`.

After live start, immediately verify:

- process is running;
- read-only reconcile status is ok;
- open positions count/coin/size;
- open orders include reduceOnly stop;
- no position without stop;
- kill-switch is inactive;
- state file contains the live entry and stop metadata.

Report only this short startup status. Do not spam every normal trade; rely on the daily 19:00 Switzerland report and critical alerts.

## User-facing trading updates

When the user asks for live-trade status or strategy explanation, answer like a trader speaking to an investor, not like a developer handoff:

- keep routine status very short unless asked for detail;
- never forward raw cronjob/supervisor JSON or background-process boilerplate as the main report; translate it into plain-language meaning such as “paper supervisor restarted, no Mainnet orders” or “old process was intentionally killed, new process is running”;
- no commit IDs, code paths, stack traces, or implementation chatter in investor-facing explanations;
- always include current positions, unrealized PnL, net account change since start when asked for profit, stop/reconcile status, and explicit `no position without stop` if relevant;
- distinguish **open unrealized PnL** from **net account value since start** because prior closed trades, fees, and funding can make them diverge;
- if a risk/profit-management feature is missing or incomplete, state it plainly and implement/fix it before implying it exists.
