Drawing Weekly and Monthly Expected-Move Lines on DAS Trader Charts

Every morning, a short Python script prices each game-plan stock’s weekly and monthly expected move from its options, then draws those edges on my DAS Trader charts as named, color-coded, global lines, without touching a chart by hand.

Why expected-move lines

The options market already prices how far a stock is likely to travel by Friday and by the monthly expiry. Those two ranges are the outside edges of a normal week and a normal month, and I want them on the chart before the open.

  • They frame targets. A long target beyond the weekly edge needs a reason, such as news or a trend day, not hope.
  • They mark where size comes in. Dealers and option sellers defend these edges, so price often stalls or reverses near them.
  • They put news in proportion. A 6% earnings gap against a 13% priced move is an underreaction, so the gap still has room. The same gap against a 4% priced move is an overreaction, so watch for a fade.

The expected move is roughly a one-standard-deviation range. Price closes inside it about two-thirds of the time. It is a frame, not a wall.

Computing the expected move

The expected move is the price of the at-the-money straddle (one call plus one put at the strike nearest the stock price), added to and subtracted from the reference close.

  1. Pick the expiry: the Friday of this week for the weekly edge and the third Friday of the month for the monthly edge.
  2. Find the strike nearest the prior close.
  3. Take the mid price (halfway between bid and ask) of that strike’s call and put, and add them: that sum is the expected move in dollars.
  4. The edges are close − move and close + move.

I anchor the weekly range on Friday’s close and keep it fixed for the whole week, so every name’s weekly line means the same thing. I use Cboe’s delayed option-chain JSON for the quotes. Outside market hours it shows the prior close, which is the reference I want.

import datetime as dt, json, re, time, urllib.request

OCC = re.compile(r"^([A-Z.]+)(\d{6})([CP])(\d{8})$")  # e.g. NVDA261106C00205000

def third_friday(d):
    for add in (0, 1):
        y, m = d.year + (d.month + add - 1) // 12, (d.month + add - 1) % 12 + 1
        f = dt.date(y, m, 15) + dt.timedelta(days=(4 - dt.date(y, m, 15).weekday()) % 7)
        if f > d:
            return f

def expected_moves(sym, today):
    url = f"https://cdn.cboe.com/api/global/delayed_quotes/options/{sym}.json"
    for wait in (0.5, 2, 6):                      # back off; Cboe throttles bursts
        time.sleep(wait)
        try:
            data = json.load(urllib.request.urlopen(url, timeout=20))["data"]
            break
        except Exception:
            data = None
    if not data:
        return {}
    ref = float(data.get("close") or data["current_price"])
    exps = {"W": today + dt.timedelta(days=(4 - today.weekday()) % 7), "M": third_friday(today)}
    book = {}
    for o in data["options"]:
        m = OCC.match(o["option"])
        if not m:
            continue
        exp = dt.datetime.strptime(m.group(2), "%y%m%d").date()
        if exp not in exps.values():
            continue
        bid, ask = float(o["bid"] or 0), float(o["ask"] or 0)
        px = (bid + ask) / 2 if bid and ask else float(o.get("last_trade_price") or 0)
        if px:
            book.setdefault(exp, {}).setdefault(int(m.group(4)) / 1000, {})[m.group(3)] = px
    out = {}
    for k, exp in exps.items():
        both = {s: v for s, v in book.get(exp, {}).items() if len(v) == 2}   # strikes with a call AND a put
        if both:
            s = min(both, key=lambda x: abs(x - ref))
            move = sum(both[s].values())
            out[k] = (round(ref - move, 2), round(ref + move, 2))
    return out

On 2026-11-02 this gave INTC a weekly range of 60.44 / 71.08 and a monthly range of 57.81 / 73.71. A stock with no weekly options (CDNA that day) returns only the monthly pair.

Drawing global lines in DAS

DAS Trader’s CMD API (a plain TCP socket, enabled under Setup → Other Configuration) has a SCRIPT <window> <script> command that runs a hotkey-style object script against a named window. That script can switch a chart’s symbol and create a horizontal trendline, then send the line to every chart of that symbol.

  1. Make a spare chart and name it. Right-click the chart header, then set Name. Mine is builder1. The script changes its symbol, so never point it at a chart you trade from.
  2. Log in on the socket: LOGIN <user> <password> <account> 0. Keep the credentials in an environment file, never in the script.
  3. Switch the chart to the symbol:
SCRIPT builder1 $c=GetWindowObj("builder1");$c.Symb="INTC";
  1. Create the line. linewidth sets the thickness, and SendToGlobal() makes the line show on every chart of that symbol:
SCRIPT builder1 $c=GetWindowObj("builder1");$l=$c.CreateTrendline("horizline");$l.name="FALCON_EM_W_HI_INTC";$l.price=71.08;$l.color="255,170,0";$l.linewidth=2;$l.Visible=1;$l.SendToGlobal();$l.save();
  1. Repeat step 4 for each edge, then step 3 for the next symbol.

Name every line, for example FALCON_<KIND>_<SIDE>_<SYMBOL>. A name lets you find or remove that one line later. The blanket RemoveAllLines command also deletes every line you drew by hand.

The object syntax (CreateTrendline, .price, .color, .linewidth, SendToGlobal, .save) comes from published DAS advanced-hotkey scripts by Peter Benci and in this one. The SCRIPT command itself is documented in the DAS CMD API manual.

The color scheme

One color always means one kind of level, on every chart. Width 2 marks a level that decides a trade; width 1 is context.

Kind Color RGB Width Meaning
EM_W orange (goldenrod) 255,170,0 2 Weekly expected-move edge
EM_M red 220,40,40 2 Monthly expected-move edge
PRIOR light gray 200,200,200 1 Prior session high and low
PM cyan 0,200,255 1 Premarket high and low
POC magenta 255,0,255 2 Prior-session volume point of control
VA violet 160,90,255 1 Value area high and low
TRIGGER green 0,220,120 2 Plan trigger
TARGET blue 0,140,255 1 Plan target
STOP pink 255,105,180 2 Plan stop or cancel level

Warm colors are option-implied ranges, cool colors are price structure, and green and pink are my own plan.

Putting it together

The morning routine runs after the game plan names its stocks, and before 08:25 CT:

  1. Read the stock list from the game-plan watchlist, minus ETFs.
  2. Price each stock’s weekly and monthly edges (the function above).
  3. Send one SCRIPT per symbol switch and one per line through the CMD API socket.
  4. Append every line to a dated log (symbol, kind, price, name, color), so the day’s chart state is on record.
import socket, time

PALETTE = {"EM_W": ("255,170,0", 2), "EM_M": ("220,40,40", 2)}

def line_script(chart, name, price, kind):
    rgb, width = PALETTE[kind]
    return (f'SCRIPT {chart} $c=GetWindowObj("{chart}");$l=$c.CreateTrendline("horizline");'
            f'$l.name="{name}";$l.price={price:.2f};$l.color="{rgb}";$l.linewidth={width};'
            f'$l.Visible=1;$l.SendToGlobal();$l.save();')

def draw(host, port, login, chart, moves):          # moves = {"INTC": {"W": (lo, hi), "M": (lo, hi)}}
    s = socket.create_connection((host, port), timeout=10)
    s.sendall((login + "\r\n").encode()); time.sleep(1)
    for sym, m in moves.items():
        s.sendall(f'SCRIPT {chart} $c=GetWindowObj("{chart}");$c.Symb="{sym}";\r\n'.encode())
        time.sleep(1.5)                              # let the chart load the symbol
        for k, (lo, hi) in m.items():
            for side, px in (("LO", lo), ("HI", hi)):
                s.sendall((line_script(chart, f"FALCON_EM_{k}_{side}_{sym}", px, f"EM_{k}") + "\r\n").encode())
                time.sleep(0.8)
    s.close()

That’s 11 stocks, 52 commands and about a minute of script time, done before I sit down at the charts.

Gotchas

  • DAS sends nothing back for SCRIPT. Success is visual: check the spare chart after the first line before you send the rest.
  • Use a dedicated chart. The script switches its symbol each time. On a chart you trade from, that would be a disaster.
  • Anchor the weekly range once. If you recompute from each day’s close, a mid-week name gets a narrower range than the rest of the board.
  • Earnings inside the window widen the straddle. That week’s range includes the event, which is useful, but label it.
  • Some stocks have no weekly options. Fall back to the monthly edge only.
  • Still untested on my setup: whether re-creating a line with the same name replaces it or duplicates it, and the exact call to delete one line by name. Test both before you automate a daily redraw.
  • Data terms: I use Cboe’s delayed quotes for a personal tool. Check the provider’s terms before you build anything on top of them.

Sources