---
title: Writing Strategies
description: The strategy() declaration, the strategy.* order API, position rules, and sizing.
verified:
  engine: 3.0.67
  probes:
    - scripts/probes/strategies/trend-brackets.ks
---

## The strategy() declaration

`strategy()` accepts everything `define()` accepts (title, position, axis, customTitle, format, maxBarsBack) plus the broker parameters below. All are optional except `title`. Arguments are literal-only and validated like `define()`; any strategy param may also be wired to a normal `input()` so users can adjust it from the script settings, with the declaration acting as the default.

```
strategy(title="MA Cross",
         initialCapital=10000,        // starting equity, > 0
         currency="USD",              // display label
         commissionPercent=0.0,       // per fill, percent of notional
         slippageBps=0.0,             // adverse, per market-crossing fill
         slippageModel="fixed",       // "fixed" | "bookEstimate"
         qtyType="percentOfEquity",   // "fixed" | "percentOfEquity" | "cash"
         qtyValue=100,                // meaning depends on qtyType
         pyramiding=1,                // max stacked same-direction entries
         fillModel="pessimistic",     // "pessimistic" | "pathHeuristic"
         instrument="spot",           // "spot" | "perps", perps since engine 3.0.63
         leverage=1,                  // perps only, > 0, since engine 3.0.63
         maintenanceMarginPercent=0.5, // perps only, >= 0 and < 100, since engine 3.0.63
         makerFeePercent=0.0,         // perps maker fills, since engine 3.0.63
         takerFeePercent=0.0,         // perps taker fills, since engine 3.0.63
         funding="data")              // "data" | "off", perps funding since engine 3.0.63
```

| Parameter | Default | Meaning |
| --- | --- | --- |
| `initialCapital` | 10000 | Starting account equity. Must be positive. |
| `currency` | "USD" | Display label for money-denominated stats. |
| `commissionPercent` | 0 | Spot commission charged on every fill as a percent of fill notional. Ignored under `instrument="perps"` with a warning if nonzero since engine 3.0.63. |
| `slippageBps` | 0 | Adverse basis points applied to market, stop, and trailing fills. Limit fills are exempt: a limit price is a bound and can fill better but never worse. |
| `slippageModel` | "fixed" | `"bookEstimate"` prices market-crossing fills from recorded order-book depth where available. See [Slippage and costs](slippage-and-costs.md). |
| `qtyType` / `qtyValue` | "percentOfEquity" / 100 | Default order sizing. `"fixed"`: `qtyValue` units per entry. `"percentOfEquity"`: `(qtyValue / 100 * equityAtFill) / fillPrice`. `"cash"`: `qtyValue / fillPrice`. Since engine 3.0.63, perps mode makes `"percentOfEquity"` and `"cash"` size margin, then notional is margin times leverage. |
| `pyramiding` | 1 | Maximum stacked same-direction entries. Excess entries are rejected and counted, never silently dropped. |
| `fillModel` | "pessimistic" | Intrabar ordering assumption when a bar could fill two levels. See [Fill simulation](fill-simulation.md). |
| `instrument` | "spot" | Since engine 3.0.63. `"spot"` uses spot accounting. `"perps"` enables isolated leveraged margin, liquidation, maker/taker fees, and optional funding. |
| `leverage` | 1 | Since engine 3.0.63. Perps leverage. Must be greater than 0. In perps mode, `percentOfEquity` and `cash` size margin, then notional is margin times leverage. |
| `maintenanceMarginPercent` | 0.5 | Since engine 3.0.63. Perps maintenance margin rate used to compute the liquidation price. Must be at least 0 and less than 100. |
| `makerFeePercent` / `takerFeePercent` | 0 / 0 | Since engine 3.0.63. Perps fee rates as a percent of fill notional. Maker applies to limit-bound fills; taker applies to market-crossing fills. Ignored by spot with a warning if nonzero. |
| `funding` | "data" | Since engine 3.0.63. Perps funding mode. `"data"` uses recorded funding settlements from the chart venue when supplied; `"off"` makes funding an exact no-op. |

Strategy scripts default to `position="onchart"` so trade markers land on the price series.

## Order API

```
strategy.entry(id, direction, qty=na, limit=na, stop=na, ocaName=na, comment=na)
strategy.close(id, comment=na)          // close the named entry (market, next open)
strategy.closeAll(comment=na)
strategy.cancel(id)                     // cancel pending unfilled orders with this id
strategy.cancelAll()
strategy.exit(id, fromEntry=na, qty=na, qtyPercent=na,
              profit=na, limit=na, loss=na, stop=na,
              trailPoints=na, trailOffset=na, ocaName=na, comment=na)
```

- `direction` is `"long"` or `"short"`. `id` is your string key; re-issuing an id replaces the pending unfilled order with that id.
- A plain `strategy.entry` is a market order for the next bar open. Adding `limit` or `stop` creates a resting order that fills when touched.
- `strategy.exit` attaches bracket legs to an entry id: `profit` and `loss` in ticks, `limit` and `stop` as absolute prices. At least one leg is required. `exit` without `fromEntry` protects all open entries. Stop, limit, and trail legs within one exit id are inherently one-cancels-all.
- `trailPoints` activates a trailing stop once the trade's favorable excursion reaches that many price units; `trailOffset` sets the trailing distance. The trail ratchets monotonically with new favorable extremes.
- `ocaName` joins any orders (entries and exits) into a one-cancels-all group: when one fills, the rest cancel immediately.
- Getters, valid on any bar: `strategy.positionSize()` (signed), `strategy.positionAvgPrice()`, `strategy.equity()`, `strategy.openProfit()`, `strategy.netProfit()`, `strategy.closedTradeCount()`, `strategy.winTradeCount()`, `strategy.lossTradeCount()`, `strategy.maxDrawdown()`.

```javascript title="trend-brackets.ks"
//@version=2
strategy(title="Trend with Brackets", initialCapital=10000, qtyType="fixed", qtyValue=1, pyramiding=1, fillModel="pathHeuristic")

timeseries bars = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries fast = sma(source=bars.close, period=5)
timeseries slow = sma(source=bars.close, period=20)
var lastClose = bars.close

if (crossover(fast, slow)) {
  strategy.entry("Trend", "long")
}
if (strategy.positionSize() > 0) {
  strategy.exit("Protect", fromEntry="Trend", stop=lastClose * 0.97, limit=lastClose * 1.05)
}

plotLine(value=fast, width=1, colors=["#4f8cff"], label=["Fast SMA"], desc=["5-period SMA of close"])
plotLine(value=slow, width=1, colors=["#8b5cf6"], label=["Slow SMA"], desc=["20-period SMA of close"])
```

## Position model

The broker nets to one position. A `strategy.entry` in the opposite direction is a reversal: it closes the existing position fully at the same fill, then opens the new one at the requested quantity. `pyramiding` caps same-direction stacking; rejected entries are counted in the output rather than thrown.

Sizing details worth knowing:

- Explicit `qty` on an order always wins over the declaration's `qtyType`.
- Sized-at-fill quantities (`percentOfEquity`, `cash`) resolve from the slippage-adjusted fill price, and fractional quantities are legal (the broker does not round lots).
- Since engine 3.0.63, in perps mode, `percentOfEquity` and `cash` size isolated margin commitment, not direct notional. Explicit `qty` and `qtyType="fixed"` remain direct quantities and are margin-checked.
- A computed size is rejected and counted when equity at fill is not positive or the quantity is not finite and positive.

Since engine 3.0.63, perps entries also pass an isolated-margin admission check before the opening leg is applied. Unrealized PnL is not collateral for adding exposure; see [Perps and Leverage](perps-and-leverage.md) for the full model.


Order methods are recorded as rejected (not thrown) when the current bar has NaN OHLC values or is not confirmed. On the last bar, unfilled orders remain visible under pending orders in the output.
