Canonical actions
Every order issued by the script uses sdk.buy(...), sdk.sell(...) or sdk.close(...) with an explicit action via the action kwarg. The engine accepts 7 canonical actions, plus a few legacy aliases (e.g. sell_short, sell_to_close_long, buy_to_cover_short) kept for backward compatibility. Prefer the canonical forms in new scripts.
| Action | Effect | When to use |
|---|---|---|
buy_to_open | Opens a long position | sdk.position == 0 and buy signal |
sell_short_to_open | Opens a short position | sdk.position == 0 and sell signal |
sell_to_close | Closes a long position | sdk.position > 0 and exit signal |
buy_to_cover | Closes a short position | sdk.position < 0 and cover signal |
close_position | Closes any open position (generic) | Forced stop, risk management |
reverse_position | Closes current and opens on the opposite side | Reversal in one step |
update_position_exits | Only updates stop/target on the live position | Trailing stop external to the engine |
action is mandatory on buy() / sell()
action is required on sdk.buy(...) and sdk.sell(...). Calling either without action raises ProtocolError and terminates the strategy:
ProtocolError: Strict Mode: buy() / sell() exige action explícitaThe rule is scoped to buy() and sell() — those are the only two methods that raise. sdk.close(...) defaults action to close_position, so it works without an explicit action, and the semantic helpers (sdk.buy_to_open(), sdk.sell_to_close(), sdk.close_position(), sdk.reverse_position(), …) each carry their own fixed verb. Passing action explicitly on buy()/sell() is still the recommended, most auditable idiom.
Full signature
All methods (buy, sell, close) accept the same kwargs. The method only determines the side on the emitted signal:
sdk.buy(
action, # REQUIRED - one of the 7 actions
qty, # quantity (float; accepts fractional in crypto)
order_type="market", # "market" (default) | "limit" | "stop"
price=None, # price for limit (or stop trigger)
stop_loss=None, # stop price attached to the position
take_profit=None, # target price attached to the position
trailing_stop_pct=None, # trailing as a percentage (0.02 = 2%)
time=None, # timestamp (ms). Default: last candle
tif="day", # "day" (default) | "gtc" | "this_bar"
size_pct=None, # fraction of cash in (0.0, 1.0] - e.g. 0.5 = 50%; alternative to qty
oco_group=None, # OCO group (one-cancels-other)
instrument_id=None, # instrument override (rare)
)The same kwargs apply to sdk.sell(...) and sdk.close(...).
Equivalent helpers
The SDK also exposes semantic shortcuts. The recommended idiom is to pass action explicitly, since it makes the code more auditable:
# All equivalent:
sdk.buy(action="buy_to_open", qty=1, order_type="market")
sdk.buy_to_open(qty=1, order_type="market")
sdk.sell(action="sell_to_close", qty=abs(sdk.position), order_type="market")
sdk.sell_to_close(qty=abs(sdk.position), order_type="market")
sdk.close(action="close_position", qty=abs(sdk.position))
sdk.close_position(qty=abs(sdk.position))1. buy_to_open
Opens a long position.
if sdk.position == 0 and buy_signal:
sdk.buy(
action="buy_to_open",
qty=1,
order_type="market",
)With attached stop and target:
sdk.buy(
action="buy_to_open",
qty=1,
order_type="market",
stop_loss=close * 0.98, # stop 2% below
take_profit=close * 1.05, # target 5% above
)The engine keeps the stop and target alive while the position is open. If either one is hit, it generates the exit signal automatically.
2. sell_short_to_open
Opens a short position.
if sdk.position == 0 and sell_signal:
sdk.sell(
action="sell_short_to_open",
qty=1,
order_type="market",
)The legacy alias sell_short is also accepted by the runtime (without the _to_open suffix), but prefer the full form for consistency with buy_to_open.
3. sell_to_close
Closes a long position. Always use qty=abs(sdk.position):
if sdk.position > 0 and exit_signal:
sdk.sell(
action="sell_to_close",
qty=abs(sdk.position),
order_type="market",
)If qty is smaller than abs(sdk.position), the engine closes partially and the remaining position stays open.
4. buy_to_cover
Closes a short position. Symmetric to sell_to_close:
if sdk.position < 0 and cover_signal:
sdk.buy(
action="buy_to_cover",
qty=abs(sdk.position),
order_type="market",
)5. close_position
Closes any open position, regardless of side. Useful for risk handlers:
# Daily stop: if drawdown > 5%, close everything
if drawdown > 0.05 and sdk.position != 0:
sdk.close(
action="close_position",
qty=abs(sdk.position),
order_type="market",
)The engine uses sdk.position to decide whether the signal becomes sell_to_close (long) or buy_to_cover (short).
6. reverse_position
Closes the current position and opens one of the opposite size. In a single atomic step:
if reversal_signal:
sdk.close(
action="reverse_position",
qty=abs(sdk.position), # qty of the current side
order_type="market",
)The reversal side is inferred: position > 0 becomes short; position < 0 becomes long. Useful in momentum strategies where the position must remain exposed.
7. update_position_exits
Updates only the stop_loss / take_profit of the live position, without emitting a new order. This is the standard mechanism for an external trailing stop:
if sdk.position > 0:
close = sdk.candles[-1]["close"]
new_stop = close * 0.97
sdk.close(
action="update_position_exits",
stop_loss=new_stop,
# take_profit omitted - keeps the previous one
)Equivalent shortcut:
sdk.update_exits(stop_loss=new_stop)
sdk.set_trailing_stop(new_stop) # simple wrapper over update_exitsLeaving stop_loss=None (or passing a value <= 0) does not clear or update the stop - the previous stop stays in place. There is no way to remove an attached stop via update_position_exits; only to overwrite it with a new positive price.
Order types (order_type)
| Value | Meaning | Extra kwargs |
|---|---|---|
"market" | Executes at market price (default) | none |
"limit" | Executes at the price or better | price=X |
"stop" | Turns into market when the trigger is touched | price=X (trigger) |
Any other value falls back to "market". For a protective stop/target, attach stop_loss / take_profit to the entry (see Order types).
# Limit order 2% below the close
sdk.buy(
action="buy_to_open",
qty=1,
order_type="limit",
price=close * 0.98,
)Time in Force (tif)
| Value | Meaning |
|---|---|
"day" | Expires at the end of the session (default) |
"gtc" | Good-til-cancelled - lives until you cancel |
"this_bar" | Valid only for the current bar; cancelled if not filled by the next candle |
Any other value falls back to "day".
Quick decision matrix
| Situation | sdk.position | Action |
|---|---|---|
| Want to open long, currently flat | == 0 | buy_to_open |
| Want to open short, currently flat | == 0 | sell_short_to_open |
| Have long, want to close | > 0 | sell_to_close |
| Have short, want to close | < 0 | buy_to_cover |
| Have anything, want to flatten | != 0 | close_position |
| Have long, want to flip to short (or vice versa) | != 0 | reverse_position |
| Want to adjust stop/target without changing size | != 0 | update_position_exits |
Common mistakes
ProtocolError: ... requires explicit action- the script calledsdk.buy(qty=1)withoutaction=. Fix by passing the canonical action.- Using
sell_shortinstead ofsell_short_to_open- the runtime accepts both for compatibility, but standardize onsell_short_to_openin new scripts. - Using
qty=1in crypto spot with a low balance - generates "insufficient capital". In crypto, computeqtyproportional tosdk.cash. - Forgetting
qty=abs(sdk.position)on closing orders - withoutqtythe order closes 1 unit, leaving the rest open. - Passing
timeas a candle index - the engine expects a timestamp in ms. Usesdk.candles[-1]["time"].