Week 06 · Build Your Own Automated Options Trading System

Your first real strategy,
coded from scratch.

A strategy is a sentence. We turn it into a function. Same input always gives the same output — testable, predictable, boring. Boring is the goal.

Week 6 of 12
One rule → one function
Tested on today & last Tuesday
This week, in one glance

Same five beats. New topic.

1
The idea
A strategy is a sentence. We turn it into a function.
2
The outcome
A function that takes today's market state and returns what it would sell.
3
Vocabulary
function · parameters · return value · if-else branching · unit test
4
Hands-on
Translate one Week-1 rule into a function. Run it on today and last Tuesday.
5
War story
"Wait for breakdown" LIMIT order filled instantly at the current price. The fix is a different order type entirely.
1 · The idea

One sentence. One function.

Your sentence (Week 1)

An IF … THEN … rule. Numbers, times, comparisons. No fuzzy words.

# your rule, in English
IF time is between 09:17 and 09:31
AND NIFTY straddle decayed ≥ 1%
THEN sell 1 lot of the ATM CE
     at a LIMIT order.

The function (Week 6)

Inputs go in, an action comes out. Pure. No surprise side effects. Easy to test.

def decide_entry(state):
    if "09:17" <= state.now <= "09:31" \
       and state.straddle_drop >= 0.01:
        return SellAction(
            symbol=state.atm_ce,
            lots=1,
            order_type="LIMIT")
    return None
A function is a sentence the bot can hear. Inputs in. Decision out. Nothing else.
2 · By the end of this week

Three boxes checked.

One of your Week-1 IF … THEN … rules now exists as a Python function in a file called strategy.py.
You can call it with mock data — "imagine NIFTY did X" — and watch what it would have done. No orders placed.
You have at least two unit tests: one where the function should fire, one where it shouldn't. Both pass on pytest.
A strategy you cannot test on yesterday's data is a strategy you cannot trust on tomorrow's.
3 · New vocabulary

Five words. Use them this week.

function
A named, reusable piece of code that takes inputs and (usually) returns an output. The cell of every program.
def decide_entry(state):
parameters
The named inputs a function accepts. The slots you fill in when you call it.
state, lots, now
return value
The single thing the function gives back. Could be a number, a string, an object, or None.
return SellAction(...)
if-else branching
The way a function reads a condition and chooses one path or another. The core of every decision.
if drop ≥ 1%: ... else: ...
unit test
A tiny piece of code that calls your function with a known input and asserts the output. Pinning behavior down.
assert decide_entry(s) is None
Five words. Six if you count pytest. By Sunday they should feel ordinary.
A real function — strategy.py

One file. One job.

strategy.py "a sentence, in Python"
# strategy.py — your first decision function

from dataclasses import dataclass

# The shape of the market state we'll pass in
@dataclass
class MarketState:
    now: str            # "09:25"
    atm_ce_symbol: str  # e.g. "NIFTY24MAY24800CE"
    open_straddle: float
    now_straddle:  float

def decide_entry(s: MarketState):
    # window check — between 9:17 and 9:31
    if not ("09:17" <= s.now <= "09:31"):
        return None

    # decay check — straddle down ≥ 1% from open
    drop = (s.open_straddle - s.now_straddle) / s.open_straddle
    if drop < 0.01:
        return None

    # fire
    return {
        "action": "SELL",
        "symbol": s.atm_ce_symbol,
        "lots": 1,
        "order_type": "LIMIT",
    }
No prices fetched here. No orders placed. The function decides. Wiring it to a broker comes in Week 7.
The two tests every function deserves

One that fires. One that doesn't.

Should fire — 9:25, decay 1.2%

s = MarketState(
  now="09:25",
  atm_ce_symbol="NIFTY 24800 CE",
  open_straddle=300.0,
  now_straddle=296.4)

action = decide_entry(s)
assert action["action"] == "SELL"
PASS · the function returned a SELL action.

Should NOT fire — 9:40 (window closed)

s = MarketState(
  now="09:40",    # too late
  atm_ce_symbol="NIFTY 24800 CE",
  open_straddle=300.0,
  now_straddle=280.0)

action = decide_entry(s)
assert action is None
PASS · the function correctly returned None.
Tests are how you make sure tomorrow's change didn't quietly break yesterday's behaviour.
4 · The hands-on bit — your task

Pick one rule. Code it. Test it.

What to do this week (2 hours, paired with Claude)

  1. Open your Week-1 page with three IF … THEN … rules. Pick the most concrete one.
  2. Ask Claude to scaffold a Python function for it. Give it: the rule text, the input fields you want to pass in, the action you want returned. Read the proposed code before you accept.
  3. Write two tests. One where the function should fire. One where it shouldn't. Save them as test_strategy.py.
  4. Run them. pytest -v. Both should pass. If one doesn't, the rule and the test disagree — figure out which is wrong before changing anything.
  5. Run the function on a real day. Look up the actual NIFTY data from today and last Tuesday. Feed each as a MarketState. Read what the function would have done.
Do not wire this to a broker yet. The point of Week 6 is the decision. The order placement is Week 7. Don't skip ahead.
5 · A real-money war story · BUG-078

"Wait for breakdown" LIMIT order filled instantly.

The rule said: wait for the price to break below ₹95, then sell. The code placed a LIMIT SELL at ₹95. The current price was already ₹103. The exchange saw a sell offer at ₹95 — much better than the bid — and matched it instantly at ₹103. No breakdown. Just an instant fill at the wrong price.

What the code did — LIMIT SELL

₹105
₹103 ← LTP (now)
₹95 — your LIMIT SELL
₹90

The exchange's logic: "sell at 95 or better." 103 ≥ 95, so it fills at 103 — better for the seller, terrible for the strategy. The breakdown never happened.

What the code should do — SL-M / SL-L

₹105
₹103 ← LTP (now)
₹95 — trigger price
₹90 — actually sells here

An SL (stop) order waits — it does nothing until LTP touches the trigger. Only then does the sell go live. This is the correct order type for "wait for breakdown."

The order-type lookup, once and for all

Order type is part of the decision.

When you want to… Order type Behavior Common mistake
Sell now, at any price MARKET Fills instantly at next available price. Indian options: not allowed via API on many segments.
Sell now, but cap the price LIMIT Fills at limit or better. If better is available, you get the better price — instantly. Using it for "wait for breakdown" — fills now, not later.
Sell only after price drops to X SL-M / SL-L Dormant until LTP hits the trigger. Then becomes a MARKET / LIMIT. Setting trigger above current price — fires immediately.
1
BUG-078 — the original incident
~8%
premium overshoot on the bad fill
SL
is the order type for "wait for breakdown"
The lesson, pinned

"Wait" is an order type, not a comment.

A LIMIT order is not a "wait until" order. The exchange will happily fill it now if the book is more generous than your limit. Waiting for a price to be crossed is what Stop orders (SL-M, SL-L) are for. The decision function must encode this choice.

So when you write your function this week and it returns an action, ask: does this action mean "fire now" or "fire when X happens"? Pick the right order type for that intent. Pick wrong and the strategy will misbehave on day one.

Every order type is a different verb. LIMIT = sell. SL = wait, then sell. They are not interchangeable.
Week 6 — takeaway

A strategy is a function
and two tests.

If you can hand someone your strategy.py and test_strategy.py, they can read the rule, run the tests, and trust the behavior. That's the real product of this week. Hooking it to a broker comes next.

End of Week 6

Next: Placing the order.

source: cowork/Course_Outline_12_Weeks · Week 6 · BUG-078
← → navigate · F fullscreen · click to advance
1 / 13