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.
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.
// 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.
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.
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.
// 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.
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.
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.
// 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.
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.
// 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.
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.
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.
// 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.
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.
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.
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.
// 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.
// 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.
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.
// 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.
// 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.
// 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.
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.
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.