CODESCRIPT

Entering and exiting

An indicator draws a line on the chart; a strategy places orders. This page shows how a position is opened, how it is closed, and how to read what came out of it.

Writing a strategy comes down to two questions: when do you get in, and when do you get out. Everything else is trim on those two sentences. Build the plainest version first and add conditions later.

When an order fills

An order cannot fill before its own bar has closed. The bar where the condition turns true is the signal bar, and a market order fills at the open of the bar after it. That is why the entry mark sits one bar to the right of the signal.

  • Conditions are measured at the bar close.
  • A market order fills at the next bar's open, and that holds for entries and exits alike.
  • A resting order behaves differently: it fills inside the bar, the moment price touches its level.
NOTE

This is not lag, it is honesty. Filling at the signal bar's close would assume you knew a price that had not printed yet, and the test would flatter you.

Going long

enterLong takes a condition and opens a long on the bar where it is true. The condition is re-measured on every bar, so you usually want a rule that is true only at the turn rather than one that stays true for a long stretch. crossover does exactly that: it is true on the single bar where two series cross.

CODESCRIPT
// hizli ortalama yavas ortalamayi yukari kesince gir
hizli = sma(close, 10)
yavas = sma(close, 30)
enterLong(crossover(hizli, yavas))
exit(crossunder(hizli, yavas))
plot(hizli, "hizli", "#26a69a")
plot(yavas, "yavas", "#ef5350")

The price panel carries a green 10-bar average and a red 30-bar one. A buy mark appears one bar after green crosses above red, and an exit mark one bar after it crosses back below.

CAUTION

While a position is open, a second enterLong condition in the same direction does nothing. By default only one entry per direction is carried; to add in stages you must raise the cap with pyramiding.

The enterLong function

enterLong(condition, stopLoss?, takeProfit?, id?, limit?, stop?)

Returns nothing; it reports a long entry order to the test.

condition
condition
The bar where this is true issues the long entry order.
stopLoss
number
Optional. A percentage stop-loss measured from the entry; writing 2 means two percent.
takeProfit
number
Optional. A percentage take-profit measured from the entry.
id
text
Optional. A constant name for the entry, used later to aim an exit at it.
limit
number
Optional. A resting buy price; it fills when price comes down to it.
stop
number
Optional. A resting breakout price; it fills when price rises to it. It cannot be combined with limit.

Going short

enterShort does the same job in the opposite direction, opening a position that gains when price falls. The exit side is still handled by exit, so there is no second closing function to learn.

CODESCRIPT
// asagi kesiste kisa pozisyon, yukari kesiste kapan
ort = sma(close, 20)
enterShort(crossunder(close, ort))
exit(crossover(close, ort))
plot(ort, "ortalama", "#2962ff")

One bar after close drops below the 20-bar average, a sell mark appears and the short opens. When close climbs back above the average the position is closed.

CAUTION

Spot mode has no short selling. enterShort produces no order there, so nothing opens even when the condition is true.

The enterShort function

enterShort(condition, stopLoss?, takeProfit?, id?, limit?, stop?)

Returns nothing; it reports a short entry order to the test.

condition
condition
The bar where this is true issues the short entry order.
stopLoss
number
Optional. A percentage stop-loss. On a short this level sits above the entry price.
takeProfit
number
Optional. A percentage take-profit. On a short this level sits below the entry price.
id
text
Optional. A constant name for the entry, used later to aim an exit at it.

Setting protection at the entry

The exit does not have to live on its own line. Pass stopLoss and takeProfit to enterLong and both protective levels are in place the moment the position opens. Both are percentages, measured from the entry price.

CODESCRIPT
// girisle birlikte zarar-kes ve kar-al kur
enterLong(crossover(close, sma(close, 20)), stopLoss=2, takeProfit=4)
plot(sma(close, 20), "ortalama", "#2962ff")

A position opens one bar after each cross. It then closes either two percent below the entry on the stop or four percent above it on the target, with no separate exit line needed.

NOTE

These levels are checked inside the bar. If both the target and the stop are touched on one bar, whichever price reaches first decides; in an exact tie the stop goes first.

Leaving: exit

The first argument of exit is always a condition. Leave it out and the script errors, because the moment of the close would be undefined. The decision is made on the bar the condition fires and the fill lands at the next bar's open.

Give the entry an id and you can aim the exit at it. With qtyPct you close a share of it instead of all of it, and that share is taken from what is left at that moment, not from the original size.

CODESCRIPT
// girise ad ver, kari iki kademede al
enterLong(crossover(close, sma(close, 20)), id='ana')
exit(crossover(rsi(close, 14), 60), fromEntry='ana', qtyPct=50)
exit(crossover(rsi(close, 14), 70), fromEntry='ana')
plot(sma(close, 20), "ortalama", "#2962ff")

The position opens after the cross. When RSI crosses above 60 half of it closes and the size steps from 1 down to 0.5; when RSI crosses above 70 the remainder goes too. Two exit marks appear at different price levels.

  • fromEntry targets only the named entry. Leave the name out and every open entry closes.
  • qtyPct says what percent of the remainder to close. Omit it and the whole lot goes.
  • Repeated 50 percent exits halve the remainder each time and never quite reach zero.
CAUTION

Misspell fromEntry and you get no warning at all. If the name matches no open entry, the exit closes nothing and the position stays open until the data runs out. This is the most common way a script goes quietly wrong.

The exit function

exit(condition, fromEntry?, qtyPct?, id?, limit?, stop?)

Returns nothing; it triggers a market exit or sets up a named resting exit order.

condition
condition
Triggers the exit on the bar where it is true. It is required.
fromEntry
text
Optional. Closes only the entry carrying this name.
qtyPct
number
Optional. What percent of the remainder closes. Omitted means all of it.
id
text
Optional. Name of the resting exit order, required whenever you pass limit or stop.
limit
number
Optional. Resting take-profit price.
stop
number
Optional. Resting stop-loss price. Given together with limit the two form a pair, and a fill on one cancels the other.

Closing everything: closeAll

closeAll takes no address. On the bar its condition fires, it closes the entire open position: staged entries, half-closed remainders, all of it. Use it where a risk condition means you want out of everything without picking.

CODESCRIPT
// asiri alim bolgesinde ne varsa birak
enterLong(crossover(close, sma(close, 20)))
closeAll(rsi(close, 14) > 75)
plot(rsi(close, 14), "RSI", "#ff9800", 1)

An orange RSI line shows in the lower panel. The position opens one bar after the average is crossed, and it is closed in full right after the first bar where RSI rises above 75.

CAUTION

Call closeAll with no condition and it closes on every single bar. In such a script no position lives longer than one bar, and the test result means nothing.

The closeAll function

closeAll(kosul?)

Returns nothing; on a true bar it closes the whole open position at market.

kosul
condition
The close condition; the whole position closes on the bar it is true. Omit it and every bar closes.

Reading the position

Sometimes the script has to know its own state: am I in a position right now, and how much of it is left? Two functions answer two different questions. position reports direction alone — +1 long, -1 short, 0 flat. positionSize reports direction and size together.

CODESCRIPT
enterLong(crossover(close, sma(close, 20)), id='ana')
exit(crossover(rsi(close, 14), 65), fromEntry='ana', qtyPct=50)
exit(crossunder(close, sma(close, 20)), fromEntry='ana')
plot(position(), "yon", "#2962ff", 1)
plot(positionSize(), "kalan", "#ff9800", 1)

Two stepped lines fill the lower panel. The blue one holds at 1 for the life of the position and drops to 0 at the exit. The orange one falls from 1 to 0.5 on the partial exit bar, which is where you watch the size shrink while the direction stays put.

  • If you only need direction as a filter, position() is enough.
  • After a partial exit positionSize() can be fractional, and that is not a fault.
  • With staged entries positionSize() climbs with the count: three open entries read +3.

The positionSize function

positionSize()

Direction times the remaining size of the open entries, which may be fractional. It is 0 when flat.

Entry and exit on the same bar

Because the conditions are written independently, both firing on one bar is perfectly ordinary. The decision always looks at the position you are actually holding, and once you know the rule the chart stops surprising you.

  • Flat: the entry wins. The exit has nothing to close, so that line quietly does nothing.
  • Long, with an exit and a fresh long entry together: the exit wins. The position closes and nothing reopens in its place.
  • Long, with an opposite signal: it reverses. The long closes and a short opens at the same price, with no flat bar in between.
  • Flat, with enterLong and enterShort both firing: long wins.
CODESCRIPT
// ayni kosul hem girise hem cikisa bagli
ort = sma(close, 20)
kesis = crossover(close, ort)
enterLong(kesis)
exit(kesis)
plot(ort, "ortalama", "#2962ff")

The first cross opens a position, because at that moment there is nothing to close. The second cross closes it and the third opens it again, so the chart shows trades that alternate open and closed.

CODESCRIPT
// ayni ortalamanin iki yonu: yukari kesiste uzun, asagi kesiste kisa
ort = sma(close, 20)
enterLong(crossover(close, ort))
enterShort(crossunder(close, ort))
plot(ort, "ortalama", "#2962ff")
plot(positionSize(), "kalan", "#ff9800", 1)

The strategy is never flat; every cross flips the direction. The orange line in the lower panel swings between +1 and -1 without passing through 0, and the closing price of the long is the opening price of the short.

TIP

If you do not want reversals, restrict the direction. With allowEntryIn permitting one side only, an opposite signal closes what is open but opens nothing new.

CODESCRIPT
// yalniz uzun tarafa izin ver
allowEntryIn("long")
enterLong(crossover(close, sma(close, 20)))
enterShort(crossunder(close, sma(close, 20)))
plot(positionSize(), "kalan", "#ff9800", 1)

The down-cross no longer opens a short; it just closes the open long. The line in the lower panel stays between 0 and +1 and never goes negative.

Reading the backtest result

When you run the script the terminal gives you two things: the entry and exit marks on the chart, and a summary. Look at the marks first and the numbers second. If the marks are not where you expected, the numbers do not matter yet.

  • Net profit: the percentage gain or loss against the starting capital.
  • Trade count: how many trades closed. High ratios built on a handful of trades can be luck.
  • Win rate: the share of trades that closed in profit.
  • Profit factor: total gains divided by total losses. Below 1 means the strategy loses money.
  • Max drawdown: how far equity fell back from its peak. This is the number you have to be able to live with.

You can read the same figures from inside the script. Plotting them is the fastest way to see which stretch of the chart actually produced the result.

CODESCRIPT
// net kar-zarar egrisini alt panoda izle
enterLong(crossover(close, sma(close, 10)))
exit(crossunder(close, sma(close, 10)))
plot(netPnl(), "net kar zarar", "#26a69a", 1)

A curve starting at zero appears in the lower panel. Where it runs flat the strategy is out of the market, and where it jumps is where trades closed.

CODESCRIPT
// kapanan islem sayisi basamak basamak artar
enterLong(crossover(close, sma(close, 10)))
exit(crossunder(close, sma(close, 10)))
plot(tradeCount(), "kapanan islem", "#787b86", 1)

A staircase that never steps down appears in the lower panel. Each step is a bar where a trade closed, and a steep staircase means the strategy trades too often.

  • equity(): the equity curve.
  • netPnl(): equity minus the starting capital.
  • tradeCount(): how many trades have closed so far.
  • winTrades() and lossTrades(): the count of winners and of losers.
  • drawdownPct(): how far below its peak the equity sits right now.
NOTE

If a position is still open when the data runs out, it is closed at the last bar's close and counted in the summary. So the final trade's exit price comes from the end of the data, not from a signal.

CAUTION

A good number on one symbol over one stretch of history proves nothing. Run the same script on other symbols and other timeframes; the result only means something if it survives there too.

Common mistakes

  • Writing exit with no condition. The first argument is required, and leaving it out makes the script fail.
  • Leaving closeAll bare. That means closing on every bar, so a position never gets to live.
  • Getting the entry name wrong in fromEntry. There is no warning, and the exit silently closes nothing.
  • Expecting the entry mark on the signal bar. It appears on the next bar, where the order actually fills.
  • Mistaking a permanently true condition for an entry rule. close > sma(close, 20) is true for hundreds of bars; use crossover to catch the turn itself.
  • Trying to read size from position(). It returns only +1, -1 or 0; positionSize() is the one that carries the amount.