---
title: Writing Strategies
description: The strategy() declaration, the strategy.* order API, position rules, and sizing.
---

> **Since engine 3.0.61; verified against engine 3.0.61.** Every example on this page is an executed probe against that build.

## 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"
```

| Parameter | Default | Meaning |
| --- | --- | --- |
| `initialCapital` | 10000 | Starting account equity. Must be positive. |
| `currency` | "USD" | Display label for money-denominated stats. |
| `commissionPercent` | 0 | Charged on every fill as a percent of fill notional. |
| `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`. |
| `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). |

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="scripts/probes/strategies/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).
- A computed size is rejected and counted when equity at fill is not positive or the quantity is not finite and positive.

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.
