agentleFS
MendurimMendurim/strikegrok-trading-desk/skills/desk-risk-limits/SKILL.md

desk-risk-limits

Skill0 starsChanged 2 months agoMITView on GitHub

How the Risk Manager agrees the desk's limits with the user, sizes each proposed trade from live account state and the market's real constraints, inspects the book, and returns a PASS with exact ticket fields or a REJECT naming the gate that failed. Use when writing or changing limits, sizing any trade, and answering "how does the book look".

2 other projects use a version of this, 1 of them edited.

Adopted by

Most recently taken up by

The file

Files
SKILL.md

Risk limits and sizing

The user decides the limits once, in writing. The Risk Manager applies them to every ticket using numbers read live. On top of both sit the market's own constraints — maximum leverage, tick and step sizes, minimum notional — which no limits file can relax.

0. Ceilings the desk will not cross

The desk carries a handful of its own ceilings. They are not advice, and they are deliberately far looser than any sensible discretionary setting. Their only job is to stop a mistyped, corrupted or over-optimistic limits file from authorising something catastrophic while nobody is watching.

CeilingValue
risk on one trade2% of equity
total open risk6% of equity
leverage on any market20x, and never above the market's own maximum
daily loss stop−10% of equity at the start of the day
a stop resting on the exchange for every entryrequired
a standing approval covering anything that can open or grow a positionnever

Exactly one kind of send may sit behind a standing approval: reduce-only protection — placing or resizing a stop on a position that has none. It cannot do anything but shrink exposure, and the alternative is a naked position waiting for somebody to read a message. Entries, adds and leverage increases always need approval against a ticket id.

The user's file may only be tighter than this table. A value looser than a ceiling is not applied. The Risk Manager returns gate failed: limits file exceeds desk ceiling <name>, keeps enforcing the ceiling, and asks the user to edit the file. The desk never edits it, and no Bot raises a ceiling.

1. Writing the limits file

Interview the user one question at a time, then write /workspace/trading-desk/risk-limits.md. Version it and date every change. Only the user changes it, in chat; the Risk Manager records who asked, when, and why.

# Risk limits v1 - 2026-09-10 - set by user

- account: the Strike account the API wallet acts for
- equity basis: equity from GET /v2/account, read live, USD
- risk per trade: 0.5% of equity            # what a hit stop costs
- total open risk: 2% of equity             # summed across open positions
- leverage cap: 3x                          # and never above the market's own maximum
- position count: 3 maximum
- allowed markets: BTC-USD, ETH-USD, ADA-USD, SOL-USD   # each must show status=trading
- stops: required on every entry, resting on the exchange, never "mental"
- daily loss stop: -2% of start-of-day equity, then no new risk until the user resets in writing
- slippage tolerance at send: 10 bps from the ticket price
- correlation: BTC, ETH and SOL count as one cluster; at most 2 positions in a cluster
- standing approvals: none                  # reduce-only protection is the one worth granting
- unprotected position deadline: 15m        # then hand it back to the user to fix in the app
- taker fee assumption: 0.045%              # note the source; correct it against realised fills
- notes:

For someone new to perpetuals, 0.25–0.5% per trade and 3x or less is a reasonable place to begin, with a minimum-size first trade in each new market. Strike's testnet has empty books and cannot serve as practice, so the first live position is the first genuine test — keep it small. Record what the user chooses and enforce it. Do not talk them up or down.

2. Sizing a trade

You need the entry, the stop, the side, the market, the current limits file, and live state. Anything missing or stale is gate failed: missing input. Never fill a gap with a guess.

2.1 Read the state, every time

  • Account — GET /v2/account for equity and margin usage, GET /v2/positions for size, entry, leverage, margin and liquidation price, GET /v2/openOrders for what is already resting including untriggered protection. See strike-account.
  • Market constraints — /v2/exchangeInfo for PRICE_FILTER.tickSize, LOT_SIZE.stepSize, MIN_NOTIONAL.notional and liquidationFee. See strike-market-data.
  • Price and depth — /v2/markPrice for the price liquidation and PnL settle against, and the Market Analyst's depth bands from /v2/depth?limit=1000.
  • Day PnL — start-of-day equity from the journal against equity now.

None of this comes from memory or from another Bot's earlier message.

2.2 The arithmetic, shown in full on every PASS

risk_usd          = equity x risk_per_trade
stop_distance     = |entry - stop|                          (must be greater than zero)
slip_stop         = assumed slippage when the stop fires, in price units
                    at minimum the current spread; widen it when depth for this size is thin
stop_fill         = stop - slip_stop   (long)  |  stop + slip_stop   (short)
taker_fee         = the rate recorded in risk-limits.md
                    a triggered stop exits at market and pays taker on the way out
                    this is a written assumption until fills exist to check it against; say so in
                    the PASS and correct it later from GET /v2/history/fill and
                    GET /v2/history/transaction (type 3 = fee)
fees_per_unit     = (entry + stop_fill) x taker_fee         (both legs)
stressed_distance = |entry - stop_fill| + fees_per_unit
raw_size          = risk_usd / stressed_distance            (never risk_usd / stop_distance)
size              = round_down(raw_size, stepSize)          (never round up)
notional          = size x entry

check  notional >= MIN_NOTIONAL                             ($10 across Strike's markets)
check  size >= one stepSize                                 (ADA-USD trades in whole tokens,
                                                             which is the commonest cause of this)
lev_cap = min(20x desk ceiling, limits.leverage_cap, the market's own maximum)
margin_needed = notional / requested_leverage               (requested_leverage <= lev_cap)
check  margin_needed <= free_margin x 0.8                   (20% headroom, or tighter if asked)
open_risk_after = sum(stressed risk of open positions) + risk_usd
check  open_risk_after <= equity x total_open_risk
check  open_risk_after <= equity x 6%                       (desk ceiling)
check  risk_usd <= equity x 2%                              (desk ceiling)
check  positions_after <= position count ; cluster within its limit
check  market allowed and status=trading ; stop present ; daily loss stop not hit

R on the ticket is the nominal stop_distance per unit, because that is the unit the user quotes targets in. The size comes from stressed_distance. The ticket carries both and says which produced what.

Why size on a stressed stop. A stop is a trigger. When it fires it becomes a market order and fills wherever the book is — worse than the trigger price, and much worse into thin depth, a gap or a cascade. Both legs pay fees too. Sizing off the nominal distance therefore prices a loss that cannot actually occur, and quietly overspends the risk budget on every single trade, always in the same direction. Sizing off the stressed distance makes the budget mean what it says.

slip_stop is an assumption, not a measurement. State the figure and where it came from, and when the depth read is stale or the size is large against the book, widen it rather than narrow it.

Worked through: equity $10,200, a 0.5% budget, ETH-USD long at 3,000 with the stop at 2,900, 3.00 of assumed slippage on the trigger (10 bps of the ticket price) and 0.045% taker on both legs. The stressed distance is 105.65 rather than 100, so the size is 0.4827 ETH rather than 0.51, and the worst case lands on exactly the $51.00 budgeted. Sized naively at 0.51 ETH the same stop costs $53.88 — 0.528% of equity against a 0.5% budget. Small, consistent, and always an overspend.

None of this is a promise. A gap straight through the stop can still cost more. That residual is the user's, and the daily loss stop is what bounds it.

2.3 Leverage and notional headroom

Three limits apply and the lowest wins: the desk ceiling, the user's cap, and the market's own maximum. Name the binding one in the PASS.

Strike also caps notional as a function of leverage. POST /v2/leverage returns the maximum notional available at the leverage it has just set, and that figure is the ceiling the ticket must fit inside. When a ticket is large relative to the account, set leverage first, read that number back, and size against it rather than assuming the headline leverage holds all the way up.

Liquidation price appears on the position once it exists. liquidationFee from /v2/exchangeInfo is part of what being liquidated actually costs. Check both afterwards and report the distance from mark to liquidation in price and in percent.

2.4 What to return

A PASS uses the block in agents/risk-manager.md: inputs with their timestamps, the sizing arithmetic, the binding leverage limit, the book after the trade, the gates, the exact ticket fields, and the next owner. A REJECT uses the same header and one line — gate failed: <gate>, <the numbers>. Either goes under ## risk in the proposal and onto the floor.

3. The book check

From GET /v2/account, GET /v2/positions, GET /v2/openOrders and /v2/markPrice:

  • equity, free margin, margin usage and ratio, and per position the distance from mark to liquidation in price and percent
  • each position: symbol, side, size, entry, mark, unrealised PnL, leverage and margin mode, margin used
  • risk to stop per position and in total, against the limits
  • protection — for each position, is a reduce-only trigger resting on the correct side (sell protects a long), sized at or above the position? Untriggered triggers report as status 5, not 2. A check that only counts status 2 will report a protected position as naked. Where there genuinely is none, that is an incident, not a line item
  • resting orders that no longer belong to any position
  • day PnL against the daily loss stop
  • funding paid today, read from GET /v2/history/funding — the amount actually charged, not a rate multiplied by an estimate of hours

Timestamp all of it. When the user wants it written down, save it under /workspace/trading-desk/briefs/YYYY-MM-DD-book.md.

4. Hitting a limit

Daily loss stop. Post it once, set status: no-new-risk in desk.md, and reject new proposals on that gate until the user resets it in writing. Exits and protection still go through.

An unprotected position. Tell the Desk Lead and Execution Trader at once. A protective stop ticket jumps the queue. If the user pre-authorised reduce-only protection it goes straight out under that standing approval. If not, the alert carries the exposure and the distance to liquidation, and once the deadline in desk.md has passed the desk tells the user to protect or close it themselves in the Strike app. Playbook D in desk-incident-response.

No limits file, or an unversioned one. The desk is a research desk until one exists.

Pitfalls

  • Sizing from a profit target, or from what the margin happens to allow. The stop sets the size.
  • Treating a triggered stop as if it fills at its trigger price. Use the stressed distance.
  • Reading a failed, empty or stale account call as a clean book. A read that did not arrive is unavailable — not "no positions" and not "no open risk". Reject on missing input rather than sizing against remembered numbers.
  • Assuming the headline leverage holds at any size. Check the notional headroom.
  • Counting correlated positions as if they were independent bets.
  • Taking a plan, a chat message or a screenshot for an open order. Only the exchange record counts.
  • Rounding a size up to clear the minimum notional. If the minimum implies more risk than the budget allows, that is a reject.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.