Automating stop-loss and target
Chapters 50-51 covered placing individual SL/SL-M orders. This chapter wires entry, stop-loss, and target into one managed unit your bot places automatically the moment an entry fills — closing the gap flagged back in chapter 47 about not sequencing dependent orders correctly.
class ManagedPosition:
def __init__(self, kite, order_manager, exchange, symbol, side, qty, product, tick_size):
self.kite = kite
self.om = order_manager
self.exchange, self.symbol, self.side, self.qty, self.product = exchange, symbol, side, qty, product
self.tick_size = tick_size
self.entry_order_id = None
self.sl_order_id = None
self.target_order_id = None
def enter(self, entry_type="MARKET", limit_price=None):
result = self.om.place(
strategy_code="managed-pos", exchange=self.exchange, tradingsymbol=self.symbol,
transaction_type=self.side, quantity=self.qty, product=self.product,
order_type=entry_type, price=limit_price,
)
self.entry_order_id = result["order_id"]
return result
def wait_for_entry_fill(self, timeout_s=15) -> bool:
status = poll_order_status(self.kite, self.entry_order_id, timeout_s=timeout_s)
return status["status"] == "COMPLETE"
def attach_stop_and_target(self, stop_trigger: float, target_price: float):
exit_side = "SELL" if self.side == "BUY" else "BUY"
filled_qty = kite_filled_quantity(self.kite, self.entry_order_id)
sl_result = self.om.place(
strategy_code="managed-pos-sl", exchange=self.exchange, tradingsymbol=self.symbol,
transaction_type=exit_side, quantity=filled_qty, product=self.product,
order_type="SL-M", trigger_price=round_to_tick(stop_trigger, self.tick_size),
)
self.sl_order_id = sl_result["order_id"]
target_result = self.om.place(
strategy_code="managed-pos-target", exchange=self.exchange, tradingsymbol=self.symbol,
transaction_type=exit_side, quantity=filled_qty, product=self.product,
order_type="LIMIT", price=round_to_tick(target_price, self.tick_size),
)
self.target_order_id = target_result["order_id"]
def handle_oco(self):
"""Call periodically: if one exit leg filled, cancel the other (manual OCO, ch 55)."""
sl_status = self.kite.order_history(self.sl_order_id)[-1]["status"]
target_status = self.kite.order_history(self.target_order_id)[-1]["status"]
if sl_status == "COMPLETE" and target_status not in ("COMPLETE", "CANCELLED"):
self.om.cancel(self.target_order_id)
elif target_status == "COMPLETE" and sl_status not in ("COMPLETE", "CANCELLED"):
self.om.cancel(self.sl_order_id)
The sequencing that matters
- Place entry.
- Wait and confirm the fill (chapter 53) before proceeding.
- Only then compute stop/target off the *actual* filled quantity and average price (chapter 56) — not the originally intended quantity.
- Poll periodically for either exit leg completing, and cancel the sibling (manual OCO — chapter 59's native GTT OCO is an alternative for less time-sensitive positions).
Skipping step 2 is exactly the bug chapter 47 flagged: placing exits before confirming the entry actually happened, and for the size that actually happened.
Next: 076 — Trailing stop-loss