OpenScriptv0.5.0Documentation
GitHub

OS7xxx Orders

The errors raised when a strategy's order cannot be placed as written: orders outside a strategy, absent or invalid quantities and prices, position rules, tags and the order destination.

On this page
  1. When they appear
  2. A strategy written to avoid them
  3. Where orders can be placed
  4. Order arguments
  5. The position and the bar
  6. Tags
  7. The destination

This page covers the OS7xxx codes of OpenScript (also called OpenAlgo Script): the refusals a strategy meets when an order cannot be placed as written. An order is the one place in the language where quietly doing something else would cost real money, so every doubtful order is refused loudly instead of being adjusted: an absent price is not replaced by the close, a price between two ticks is not rounded, and an entry the declaration forbids is not added to the position. Each refusal points at a line you can fix, and most have a standard guard that keeps a strategy from ever reaching them.

When they appear

WhenCodesWhat happens
When the script compilesOS7001, OS7003, OS7016Shown in the console under the editor. The strategy cannot run until it is fixed
When an order is placed on a barOS7002, OS7004, OS7006 to OS7010, OS7013, OS7017The run stops at that bar, and nothing further is sent
Not raised in version 0.5.0OS7005, OS7011, OS7012, OS7014, OS7015, OS7018, OS7019Reserved for checks that arrive later. Each entry says what happens today

A refusal while the run is going stops it at that bar in every place a strategy runs:

  • On the chart, where a strategy's plots and trades are simulated in the browser, the strategy draws nothing and /trading shows the code and the message in a notice, as for any runtime error.
  • In the Backtest panel, the report lists only the trades made before the refusal, the equity curve runs flat from there to the end, and the panel does not show the error. A backtest with far fewer trades than the chart suggests is worth checking for one first. See Backtesting.
  • In a deployment from the Strategies panel, sandbox trading (analyzer mode in OpenAlgo) or live, the run stops at that bar, sends nothing further, and writes the code and the bar to the run's log on the server. See Sandbox and live.

A strategy written to avoid them

This strategy enters on an EMA cross, sets a protective stop at the previous 20 bar low, and exits on the opposite cross or a close below the stop. Every guard in it is there because of one of the codes below.

version 1
strategy("EMA cross, guarded", overlay = true)

qty = input(1, "Quantity", min = 1)
fast = ema(close, 9)
slow = ema(close, 21)
up = crossUp(fast, slow)
down = crossDown(fast, slow)

// The low of the previous 20 bars, rounded onto the tick (OS7006). It is
// absent on the first bars, and on a chart with no tick size (OS7002).
swingLow = roundToTick(lowest(low, 20)[1])

var stopAt = none

// Enter only when flat, so pyramiding never refuses an entry (OS7008).
// Entry and exit sit in one if chain, so they never run on the same bar.
if up and pos.isFlat and not isNone(swingLow)
    buy(qty = qty, tag = "cross")
    // A protective stop goes below a long entry (OS7010).
    exit(tag = "cross", stop = swingLow)
    stopAt = swingLow
else if pos.isLong and (down or close < stopAt)
    // No quantity: close takes whatever is left to close (OS7017).
    close(tag = "cross")

plot(fast, "Fast EMA", aqua)
plot(slow, "Slow EMA", orange)
plot(pos.isLong ? stopAt : none, "Stop", red, style = "step")

The crosses are computed at the top level, above the if, because a stateful call such as crossDown() inside a branch only advances on the bars where the branch runs (warning OS8001). The stop is also tested by the script itself, because the version 0.5.0 backtest does not fill a stop set with exit(); see Exits and brackets.

Where orders can be placed

OS7001 Only a strategy can do that

Errorcheck

name is available only in a file declared with strategy().

What it means

The order calls, buy(), sell(), exit(), close(), cancel() and the order.* functions, and the pos.* values need a position to act on and a report to write to. Only a file declared with strategy() has those, so a study that uses one is refused when it compiles, and a study can never place an order on any bar.

Change study(...) to strategy(...) when the script is meant to trade. When you only want to mark the bar on the chart, keep the study and use signal() instead. See Strategies overview.

How to fix it

Change study(...) on line line to strategy(...), or replace name with signal("...") to mark the bar without trading.

Before

study("EMA cross", overlay = true)

if crossUp(fast, slow)
    buy(qty = 1)

After

strategy("EMA cross", overlay = true)

if crossUp(fast, slow)
    buy(qty = 1)

OS7003 An order function inside a request expression

Errorcheck

name inside a request expression would place an order from another instrument's bars.

What it means

The expression inside req.timeframe() or req.symbol() runs on other bars, in their own time: the daily bars of a "1D" read, or another instrument's bars. An order placed there would have no instrument, no moment and no price of its own, and it would fire once per bar of a series the chart never shows.

Read the value you need with the request, and place the order at the top level of the strategy from the result, as the fix below does.

How to fix it

Read the value with the request, and place the order at the top level from the result.

Before

d = req.timeframe("1D", buy(qty = 1))

After

up = req.timeframe("1D", close > ema(close, 20))
if up
    buy(qty = 1)

Order arguments

OS7002 An order argument is absent

Errorruntime

name's argument is absent on this bar.

What it means

An order argument that comes out absent is refused rather than defaulted, because an order is the one place where quietly doing nothing, or something else, is worse than stopping. The usual source is warmup: a stop, a limit or a quantity computed from an indicator that has no value on the first bars, such as lowest() over 20 bars before 20 bars exist. A missing instrument fact does it too: roundToTick() is absent where the host states no tick size, and chart.lotSize is absent on the /trading chart.

Leaving an argument out is different: buy() with no quantity uses the declaration's qty. Test a computed value with isNone() before the order call, as the example above does with swingLow, or give it a fallback with orElse() only where a fallback is genuinely correct. See Absent values.

How to fix it

Guard the call with isNone(argument), or supply a fallback with orElse() where one is genuinely correct.

Before

buy(qty = 1, stop = lowest(low, 20))

After

s = lowest(low, 20)
if not isNone(s)
    buy(qty = 1, stop = s)

OS7004 Order quantity is zero or negative

Errorruntime

name was given a quantity of qty.

What it means

The direction of an order comes from the function, buy() or sell(), never from the sign of the quantity. A negative quantity is a calculation that went the wrong way, and a quantity of zero is never what a script means, so both stop the run. The usual cause is sizing towards a target, target - pos.size, on a bar where the target has already been reached or passed.

Test the size before the call, and use sell() to go the other way. See Position and sizing.

How to fix it

Pass a positive quantity, guard the call with a size test, and use sell() to go the other way.

Before

buy(qty = target - pos.size)

After

delta = target - pos.size
if delta > 0
    buy(qty = delta)

OS7005 Quantity is not a multiple of the lot size

Errorruntime

symbol trades in lots of lot, and qty is not a multiple of it.

What it means

NFO futures and options, and MCX contracts, trade in lots: an order must be a whole multiple of the contract's lot size, and the exchange rejects anything else. This code is planned to refuse such a quantity in the engine too, so a backtest never reports a trade that could not have happened.

Not raised yet. In version 0.5.0 nothing compares an order's quantity with the lot size, so buy(qty = 100) on a contract whose lot is 75 units is sent as written. Size in lots yourself: declare qtyType = "lots" and pass a number of lots, as the fix below does (with a lot of 75, buy(qty = 2) is 150 units). The fix line also names order.roundToLot(), which is planned and does not compile in this release; until it arrives, round a computed quantity down to whole lots with floor(qty / lot) * lot.

How to fix it

Size in lots: declare qtyType = "lots" and pass the lot count, or round a computed size with order.roundToLot().

Before

buy(qty = 100)

After

strategy("Lots", qtyType = "lots")
buy(qty = 2)

OS7006 Price is not on a tick

Errorruntime

symbol ticks at tick, and price does not fall on one.

What it means

Every instrument trades in steps of its tick size, and a limit or stop price between two ticks cannot exist at the exchange. The engine does not round it for you, because moving the order off the level your script computed would change the result, and in a backtest the change would often be in your favour. A price computed as a percentage, such as close * 1.013, is the usual cause.

Round the price onto the tick with roundToTick() before the call, as the example above does. Where the host states no tick size, roundToTick() is absent, so test the rounded price once with isNone() and use it everywhere. See Orders.

How to fix it

Round to the tick before passing the price: round(price / chart.tickSize) * chart.tickSize.

Before

sell(qty = 1, limit = close * 1.013)

After

target = round(close * 1.013 / chart.tickSize) * chart.tickSize
sell(qty = 1, limit = target)

OS7007 A resting order has no price

Errorruntime

A type order needs argument, and none was given.

What it means

A limit or stop order rests at a price level, so it needs that level. order.place() with type = "limit" needs price, type = "stop" needs trigger, and type = "stopLimit" needs both. The engine does not fill in the bar's close, because that would turn the order into a market order under another name, and the report would say "limit" about a fill the script never asked for.

Pass the price the type needs, or use type = "market" and let the order fill at the next price.

How to fix it

Pass argument, or leave the type as market and let the order fill at the next price.

Before

order.place("buy", 1, type = "limit")

After

order.place("buy", 1, type = "limit", price = close - chart.tickSize)

The position and the bar

OS7008 The entry was refused by pyramiding

Errorruntime

This strategy allows max entries in one direction and already holds found.

What it means

pyramiding in the declaration says how many entries in one direction a position may hold at once, and it is 1 unless you set it. An entry beyond that stops the run, rather than quietly building a bigger position than the declaration allows and reporting a return the stated rules never earned. The usual cause is an entry condition that stays true for several bars with no position guard.

A close and a new entry in the same direction on one bar cause it too: an order fills after the bar that places it, so when the entry is placed the position has not closed yet and still counts. Guard entries with pos.isFlat, as the example above does, or raise pyramiding when adding to a position is the plan. See Declarations.

How to fix it

Raise pyramiding in the declaration, or test pos.size before entering again.

Before

strategy("Add", pyramiding = 1)
if signalUp
    buy(qty = 1)

After

strategy("Add", pyramiding = 1)
if signalUp and pos.size == 0
    buy(qty = 1)

OS7010 A bracket price is on the wrong side of the entry

Errorruntime

A side entry at entry cannot take a leg at price.

What it means

A stop protects a position and a target takes profit, so their sides are fixed by the position's direction. For a long, the stop goes below the average entry price and the target above it; for a short, the other way round. A stop on the wrong side would fill at once and turn every trade into an instant loss that looks like a strategy result.

The check is made against an open position, so an entry and its stop placed on the same bar, before the entry has filled, are the ordinary shape and are not refused, and a level exactly at the entry is allowed. See Exits and brackets.

How to fix it

Put the stop below a long entry and the limit above it, and swap the two for a short.

Before

if pos.isFlat
    buy(qty = 1)
else
    exit(limit = pos.avgPrice - atrValue, stop = pos.avgPrice + atrValue)

After

if pos.isFlat
    buy(qty = 1)
else
    exit(limit = pos.avgPrice + atrValue, stop = pos.avgPrice - atrValue)

OS7011 The order needs more capital than the strategy has

Errorruntime

This order needs required and the strategy has available.

What it means

A backtest that could spend money it does not have would report returns nobody could have earned. This code is planned to refuse an order that needs more capital than the strategy has left, and to record the refusal so the equity curve stays honest.

Not raised yet. In version 0.5.0 nothing compares an order's cost with the strategy's capital, so buy(qty = 100) at a price near 100 fills in full under capital = 1000. Keep quantity times price within the capital you declared yourself. Neither route the fix names works in this release: the backtest refuses qtyType = "equityPercent" with OS6021, and pos.equity is planned. In the Backtest panel count in units or lots.

How to fix it

Size from equity with qtyType = "equityPercent", or test pos.equity before entering.

Before

buy(qty = 100)

After

strategy("Sized", qtyType = "equityPercent")
buy(qty = 10)

OS7012 The instrument is outside its session

Errorruntime

symbol is outside its trading session at time.

What it means

An exchange works orders only during its session, 09:15 to 15:30 IST for NSE equities and NFO contracts. This code is planned to refuse an order placed outside the session, rather than hold it until the open and fill it at a price the script never saw.

Not raised yet. In version 0.5.0 nothing checks the session before an order is sent. Guard entries yourself with session.isIn(), as the example below does, and name the zone, as in session.isIn("0915-1530", "Asia/Kolkata"): the /trading Backtest panel states no timezone, so there a window without one is absent and the guard never lets an entry through. session.isOpen, which the fix line names, is planned and does not compile in this release. The declaration's closeOnSessionEnd = true is accepted but not yet acted on either, so close an intraday position yourself before 15:30, as Exiting on the clock shows. See Sessions and time.

How to fix it

Guard entries with session.isOpen, and set closeOnSessionEnd = true to flatten at the close.

Before

if crossUp(fast, slow)
    buy(qty = 1)

After

if crossUp(fast, slow) and session.isIn("0915-1530")
    buy(qty = 1)

OS7013 Two opposite orders on one bar

Errorruntime

first and second were both placed on bar bar.

What it means

When one bar places both a buy and a sell, there is no fair way to choose between them: which one comes first in the file is an accident of layout, and "the last one wins" would change silently when someone reorders two blocks. So neither is placed, the run stops, and the message names both calls with their lines, and the bar. It happens with two separate if blocks whose conditions can be true on the same bar, such as a buy() on an EMA cross and a sell() on an RSI level, as in the example below.

Make the conditions exclusive with else if, as the fix does, or place the sell on this bar and the buy on the next. Two orders on the same side are not this error, and neither is a close() or an exit() beside a buy() or a sell(): the code is about buy() and sell() only.

How to fix it

Make the conditions exclusive with else if, or place the exit on this bar and the entry on the next.

Before

r = rsi(close, 14)
if crossUp(fast, slow)
    buy(qty = 1)
if r > 70
    sell(qty = 1)

After

r = rsi(close, 14)
if crossUp(fast, slow)
    buy(qty = 1)
else if r > 70
    sell(qty = 1)

OS7017 A close states more than it is closing

Errorruntime

close was given a quantity of qty, and part has held left to close.

What it means

A close can never send more than is left to close, because an order that went past zero would flatten the position and open the opposite one under a call named close. What is left is what the position, or the part of it the tag names, holds, less any orders already on their way out. close(qty = 5) against a position of 1 stops the run, naming both numbers.

Leave the quantity out and close() closes whatever is left, which cannot be wrong, or size a partial exit from pos.size. A close with no quantity on a tag that holds nothing sends nothing and is not an error.

How to fix it

Leave the quantity out and close() flattens what is left, or size the part from pos.size and keep the quantity at or under held.

Before

if pos.isFlat
    buy(qty = 1)
else
    close(qty = 5)

After

if pos.isFlat
    buy(qty = 1)
else
    close()

Tags

A tag is the name you give an order with tag = "...", such as buy(qty = 1, tag = "breakout"). Later calls use it to say which order or which part of the position they mean.

OS7009 Unknown order tag

Errorruntime

There is no working order tagged tag.

What it means

cancel() acts on a working order: one placed and not yet filled, cancelled or expired. A tag that names no working order means the script has lost track of its orders, most often because the order has already filled. Ignoring the call would leave the strategy believing an order is still out, so the run stops.

Use the tag the order was placed with, keep your own record of whether the order is still working, or call cancelAll() when you mean every working order.

How to fix it

Use the tag the order was placed with, or cancelAll() where the script means every order it has working.

Before

buy(qty = 1, tag = "entry")
cancel("entries")

After

buy(qty = 1, tag = "entry")
cancel("entry")

OS7016 A close names a tag nothing places

Errorcheck

No order in this file is placed with the tag tag.

What it means

close() with a tag closes the part of the position that orders placed with that tag opened. When no order anywhere in the file is placed with that tag, the close can never close anything: it would send nothing on every bar and say nothing, while the strategy believes it has flattened. That is almost always a typo, so the compiler refuses it.

Use the tag the entry was placed with, or leave the tag out to close the whole position. A tag the script computes is not checked, because the compiler cannot know its value.

How to fix it

Use the tag the entry was placed with, or leave the tag out to flatten the whole leg.

Before

if pos.isFlat
    buy(qty = 1, tag = "entry")
else
    close(tag = "entries")

After

if pos.isFlat
    buy(qty = 1, tag = "entry")
else
    close(tag = "entry")

The destination

The destination is where orders go: the simulator in the Backtest panel, or sandbox trading (analyzer mode in OpenAlgo) or a live account when a strategy is deployed. The last four codes are about the conversation between the engine and the destination, and none is raised in version 0.5.0.

OS7014 The destination rejected the order

Errorhost

The order destination rejected name: reason.

What it means

The order left the strategy well formed and the destination refused it: a product the account cannot trade, not enough margin, or a symbol the account has no permission for. The reason comes from the destination, not from the script, and the same order will be refused again until the account or the order changes.

Not raised yet. In version 0.5.0 a refusal that comes back is recorded against the order as rejected, with the destination's own reason, but no diagnostic points at the line that placed it. See Sandbox and live.

How to fix it

Act on reason: it comes from the destination, not from the script, and the same order will be rejected again until the account or the order changes.

Before

strategy("Swing", product = "overnight")
buy(qty = 1)

After

strategy("Swing", product = "intraday")
buy(qty = 1)

OS7015 The strategy has no order destination

Errorhost

This strategy placed an order and the host supplied no destination.

What it means

A strategy needs somewhere for its orders to go. This code is planned for a host that runs a strategy with nowhere to send orders, which would compute a position nobody ever took.

Not raised yet. In version 0.5.0 nothing raises OS7015. An engine given no order route at all refuses a strategy when it loads, with OS6006 naming orders. In /trading a strategy always has a destination: the simulator when you backtest it, and the one its deployment names when it runs.

How to fix it

Connect a destination in the host, or run the file as a study(): replace buy() with signal("BUY").

Before

strategy("EMA cross")
if crossUp(fast, slow)
    buy(qty = 1)

After

study("EMA cross")
if crossUp(fast, slow)
    signal("BUY")

OS7018 A frame names an order this strategy did not place

Errorhost

The frame names intent intent, and this strategy holds no such order.

What it means

A destination reports on an order by the id the engine gave it when the order was sent. This code is for a report naming an order this strategy never placed, such as a destination answering for another strategy's order or for a run that has already ended. It is a problem in the host, not in your script.

Not raised yet. In version 0.5.0 such a report is refused and the refusal is recorded, but no diagnostic is raised. It concerns you only if you build your own host on the library: answer with the id the engine sent. See Host interface.

How to fix it

Answer with the intent id the engine sent. A destination's own reference is carried in the frame's reference field, where the engine records it and never parses it, and it is not what an answer is addressed by.

Before

engine sent: intent 7, intent 8
frame: intent 11, status filled, filled qty 1

After

engine sent: intent 7, intent 8
frame: intent 8, status filled, filled qty 1

OS7019 A fill was reported with no price

Errorhost

The frame reports qty filled for intent intent, and no average fill price.

What it means

A report that says more quantity has filled must also carry the average fill price, because a position needs a price as well as a size before it has an average entry, a profit or an equity point. A report with a quantity and no price is refused whole.

Not raised yet. In version 0.5.0 such a report is refused and recorded, but no diagnostic is raised. Like OS7018, it concerns hosts built on the library: report the destination's average fill price over the whole filled quantity on every report that adds quantity.

Related. Orders, Exits and brackets, Position and sizing, Backtesting, Sandbox and live, Reading an error

How to fix it

Report the average fill price the destination computed over the cumulative quantity, on every frame that reports a quantity greater than the last one. A frame carrying no new quantity needs no price.

Before

frame: intent 8, status filled, filled qty 3, average fill price absent

After

frame: intent 8, status filled, filled qty 3, average fill price 104.25