Skip to main content

Stopping trading

Three levels of stop, least to most drastic. Pick by how much you trust the system right now.

Everything here works whether or not the engine is running. Halt state is a file under the state root, and flatten talks straight to the broker — so a wedged engine, a hung process, or a machine you have already killed does not stand between you and stopping.

Level 1 — halt one strategy

Use when a strategy is behaving oddly but you have no reason to distrust the positions.

tradeflow halt VolumeSpikeStrategy --reason "entries look wrong since 14:00"
  • New entries for that strategy are refused.
  • Existing positions stay open, and their broker-side bracket legs stay protective.
  • Exits still work — see below.

Level 2 — halt everything

Use when something system-wide looks wrong and you want to think before acting.

make halt REASON="feed quality" # or: tradeflow halt all --reason "..."
tradeflow halts # what is currently in force

Positions remain open. Nothing is closed.

Level 3 — flatten

Use when you do not trust the system, or a strategy is losing in a way you cannot diagnose quickly.

make flatten REASON="unexplained drawdown"
# or: tradeflow flatten --confirm --reason "..."

In this order, deliberately:

  1. Halt — recorded first. Cancelling and closing while an engine is still streaming bars is a race the engine can win: it re-enters on the next bar and the account refills behind you.
  2. Cancel every open order.
  3. Close every position.
  4. Read the account back and report what is actually still open.

Every step is attempted even if an earlier one failed, because a partial flatten beats stopping halfway and leaving positions open. The report says exactly which steps succeeded, and the command exits non-zero if any did not.

Submitted is not closed

A close is a request. Outside market hours it queues, and at the open it fills piecemeal — so there is an interval, minutes wide at best, where every close has been accepted and the account still holds everything. The report therefore separates the two facts and never calls the book flat without a broker read that saw it:

FLATTEN
halt set : yes
orders cancelled : yes
close orders submitted : yes
positions observed closed : pending
last broker position check: 2026-01-02T14:31:07+00:00
remaining positions : 4 (AAPL, KO, MSFT, PFE)
resting orders observed : 0

NOT FLAT — the close orders were accepted and these positions are
still open. A queued close fills at the next open; a *refused* one
never will, and this cannot tell them apart. Re-run to re-read, and
check the symbols above at the broker if they persist.

positions observed closed is four-valued, because the four mean different things to whoever has to act:

ValueMeaning
yesa broker read came back with no positions — the only state that counts as flat
pendingthe closes were accepted and positions are still open; they may fill later
nothe close request itself failed, and positions are still open
unknownthe confirming read failed, so nothing here knows what is open

Both legs are observed, not assumed. The cancel is a submitted fact too, so the same read reports how many orders are still resting — a resting order the cancel missed can refill the book after the instant the read was taken. unknown there means the order book could not be read.

The command exits zero only when the halt is recorded and a broker read saw no positions and saw no resting orders. Everything else exits non-zero. A script that treated exit 0 as "flat" was previously being told only that the close request had been accepted.

Note the case this makes newly honest: if the cancel call failed but the read finds no positions and no resting orders, that is reported as flat — with the failed call still listed — because the account is verifiably in the terminal state. The old logic called it incomplete on the strength of a call that failed over an order book which turned out to be empty anyway.

A halt never blocks an exit

Halts refuse entries only. This is deliberate and worth understanding, because it is the difference between a switch you will pull and one you will hesitate over:

  • A halt that also blocked closing orders would trap the book at exactly the moment someone decided to stop.
  • flatten sets a halt and then closes positions. If the halt blocked exits, it would deadlock against its own gate.

Every entry a halt refuses is counted under one halted row in tradeflow execution-report, however many separate halts were in force over the period. The row keeps one example message, so the halt's reason, actor and time are still there to read — the grouping only stops one stop from looking like several different problems.

Resuming

tradeflow resume all # or a strategy name

Then restart the engine. Nothing resumes on its own — that is the point of the state being durable.

After a flatten

  1. Verify at the broker that you are actually flat. The report is what this system believes; the broker is what is true.
  2. tradeflow halts — confirm the halt is in force.
  3. tradeflow reconcile — compare the ledger against the account.
  4. tradeflow execution-report — what the live path actually did: slippage, latency, submitted-versus-filled notional, and which refusals stopped a signal.
  5. Diagnose, fix, then tradeflow resume all.

Practise it

Run a flatten drill on paper, roughly monthly, while actually holding a position:

tradeflow flatten --confirm --reason "monthly drill"

Check that the position closed, tradeflow halts shows the halt, and resume clears it. An emergency procedure nobody has run is a procedure nobody knows works — find out on a day you chose, not on the day you need it.

What stopping does not do

  • It does not close positions you opened by hand outside the engine; close those yourself.
  • It does not stop the engine process. Ctrl-C or SIGTERM does that — see stopping a session. One signal is enough, and the exit reports what was still open.
  • It does not undo fills. Slippage is real and irreversible.