PageSourceSearch

https://www.tradeleaks.ai/js/trade-engine.js?v=6

js tradeleaks.ai collected 2026-10-05 15:55:08 UTC 29,986 bytes, 798 lines download raw bytes

1(function () {
2  "use strict";
3
4  const MOUNT_ID = "tl-trade-engine";
5  const PATH = "/trade-engine";
6  const PRESETS = [
7    { id: "all", label: "All ideas" },
8    { id: "bullish_breakout", label: "Bullish breakout" },
9    { id: "sell_put_income", label: "Sell put income" },
10    { id: "high_iv", label: "High-IV setup" },
11    { id: "biotech_binary", label: "Biotech binary" },
12    { id: "earnings_move", label: "Earnings move" },
13    { id: "ma_rumor", label: "M&A rumor" },
14  ];
15
16  function isEngine() {
17    const p = (location.pathname || "/").replace(/\/+$/, "") || "/";
18    return p === PATH;
19  }
20
21  function isPro() {
22    try {
23      const u = JSON.parse(localStorage.getItem("tradeleaks_user") || "null");
24      return !!(u && (u.isPro || String(u.membership || "").toLowerCase() === "pro"));
25    } catch (_) {
26      return false;
27    }
28  }
29
30  function esc(s) {
31    return String(s ?? "")
32      .replace(/&/g, "&")
33      .replace(/</g, "&lt;")
34      .replace(/>/g, "&gt;")
35      .replace(/"/g, "&quot;");
36  }
37
38  function money(n, digs) {
39    if (!Number.isFinite(n)) return "—";
40    return new Intl.NumberFormat("en-US", {
41      style: "currency",
42      currency: "USD",
43      minimumFractionDigits: digs ?? 0,
44      maximumFractionDigits: digs ?? 0,
45    }).format(n);
46  }
47
48  function pct(n) {
49    if (!Number.isFinite(n)) return "—";
50    return `${n >= 0 ? "+" : ""}${n.toFixed(1)}%`;
51  }
52
53  function matchesPreset(presets, active) {
54    if (active === "all") return true;
55    return Array.isArray(presets) && presets.includes(active);
56  }
57
58  function ensureNav() {
59    const links = [...document.querySelectorAll("a")];
60    if (links.some((a) => (a.getAttribute("href") || "").replace(/\/+$/, "") === PATH)) return;
61    const opt = links.find((a) => /option-flow/i.test(a.getAttribute("href") || ""));
62    if (!opt) return;
63    const a = document.createElement("a");
64    a.href = PATH;
65    a.textContent = "Trade Engine";
66    a.className = opt.className;
67    a.addEventListener("click", (e) => {
68      e.preventDefault();
69      history.pushState({}, "", PATH);
70      mount();
71    });
72    opt.parentElement?.insertBefore(a, opt.nextSibling);
73  }
74
75  function findMain() {
76    return (
77      document.querySelector("main") ||
78      document.querySelector("#root > div > div:last-child") ||
79      document.getElementById("root")
80    );
81  }
82
83  function hideReactPage(main) {
84    [...main.children].forEach((ch) => {
85      if (ch.id === MOUNT_ID) return;
86      ch.style.display = "none";
87      ch.setAttribute("data-tl-te-hidden", "1");
88    });
89  }
90
91  function showReactPage(main) {
92    [...main.querySelectorAll("[data-tl-te-hidden='1']")].forEach((ch) => {
93      ch.style.display = "";
94      ch.removeAttribute("data-tl-te-hidden");
95    });
96    const host = document.getElementById(MOUNT_ID);
97    if (host) host.remove();
98  }
99
100  function modeLabel(m) {
101    return (
102      {
103        buy_call: "Buy Call",
104        buy_put: "Buy Put",
105        sell_call: "Sell Call",
106        sell_put: "Sell Put",
107      }[m] || m
108    );
109  }
110
111  function card(title, badge, rows, highlight) {
112    return `<div class="te-card${highlight ? " is-hi" : ""}">
113      <div class="te-card-h"><span>${esc(title)}</span>${badge ? `<span class="te-badge">${esc(badge)}</span>` : ""}</div>
114      <div class="te-card-b">${rows
115        .map(
116          (r) => `<div class="te-row"><span>${esc(r.label)}</span><strong class="${r.cls || ""}">${esc(r.value)}</strong></div>`
117        )
118        .join("")}</div>
119    </div>`;
120  }
121
122  function optionRows(opt, isSell, accentRoi) {
123    if (!opt) return [];
124    const rows = [
125      { label: "Strike", value: `$${opt.strikePrice}` },
126      { label: "Expiry", value: `${opt.expiryDate} (${opt.daysToExpiry}d)` },
127      {
128        label: isSell ? "Collateral / Contract" : "Premium / Contract",
129        value: money(opt.costPerContract, 2),
130      },
131      { label: "Contracts", value: Number(opt.contracts || 0).toLocaleString() },
132      { label: isSell ? "Capital Tied" : "Invested", value: money(opt.invested) },
133      {
134        label: isSell ? "Credit Received" : "Value at Target",
135        value: money(opt.payoffAtTarget),
136      },
137      {
138        label: "Profit",
139        value: money(opt.profit),
140        cls: opt.profit > 0 ? "pos" : opt.profit < 0 ? "neg" : "",
141      },
142      {
143        label: "ROI",
144        value: pct(opt.roi),
145        cls: opt.roi > 0 ? (accentRoi ? "pos accent" : "pos") : opt.roi < 0 ? "neg" : "",
146      },
147    ];
148    if (opt.impliedVolatility != null) {
149      rows.push({
150        label: "IV",
151        value: `${(Number(opt.impliedVolatility) * 100).toFixed(1)}%`,
152      });
153    }
154    if (opt.delta != null) {
155      rows.push({ label: "Delta", value: Number(opt.delta).toFixed(2) });
156    }
157    if (opt.theta != null) {
158      rows.push({
159        label: "Daily Theta",
160        value: `$${(Number(opt.theta) * 100).toFixed(2)}/contract`,
161      });
162    }
163    return rows;
164  }
165
166  function renderResults(host, data) {
167    const s = data.stockScenario || {};
168    const o = data.optionsAnalysis || {};
169    const v = data.verdict || {};
170    const mode = data.tradeMode || "buy_call";
171    const isSell = mode === "sell_call" || mode === "sell_put";
172    const isPut = mode === "buy_put" || mode === "sell_put";
173    const isCovered = mode === "sell_call";
174    const downside = mode === "buy_put";
175    const best = o.bestROIOption;
176    const profit = o.bestProfitOption;
177    const cheap = o.cheapestOption;
178    const top = o.topOptions || [];
179    const mLabel = modeLabel(mode);
180    const recCls =
181      v.recommendation === "options"
182        ? "te-rec-opt"
183        : v.recommendation === "mixed"
184          ? "te-rec-mix"
185          : "te-rec-stock";
186    const recLabel =
187      v.recommendation === "stock"
188        ? downside
189          ? "Short Shares"
190          : isCovered
191            ? "Hold Shares"
192            : "Buy Shares"
193        : v.recommendation === "options"
194          ? isCovered
195            ? "Sell Covered Call"
196            : mLabel
197          : "Mixed Approach";
198    const sharesTitle = downside
199      ? "Short Shares"
200      : isCovered
201        ? "Shares (Long Book)"
202        : "Shares";
203    const moveCls = downside ? "neg" : "pos";
204    const modeCls = isSell ? "sell" : isPut ? "put" : "buy";
205
206    const cards = [];
207    cards.push(
208      card(sharesTitle, "baseline", [
209        { label: "Shares", value: Number(s.shares || 0).toLocaleString() },
210        { label: "Invested", value: money(s.invested) },
211        { label: "Cash Remaining", value: money(s.cashRemaining) },
212        { label: "Value at Target", value: money(s.targetValue) },
213        {
214          label: "Profit",
215          value: money(s.profit),
216          cls: (s.profit || 0) > 0 ? "pos" : (s.profit || 0) < 0 ? "neg" : "",
217        },
218        { label: "ROI", value: pct(s.roi), cls: (s.roi || 0) > 0 ? "pos" : "" },
219      ])
220    );
221    if (best) {
222      cards.push(
223        card(
224          "Best Return",
225          best.label || "Best Return",
226          optionRows(best, isSell, best.roi > (s.roi || 0)),
227          v.recommendation === "options"
228        )
229      );
230    }
231    if (cheap) {
232      cards.push(
233        card(
234          isSell ? "Richest Credit" : "Cheapest Profitable",
235          isSell ? "max credit" : "low cost",
236          optionRows(cheap, isSell, false)
237        )
238      );
239    }
240    if (profit) {
241      cards.push(
242        card("Most Profit ($)", "max profit", optionRows(profit, isSell, false))
243      );
244    }
245
246    const rows = top
247      .map(
248        (r, i) => `<tr class="${i === 0 ? "is-top" : ""}">
249      <td>$${esc(r.strikePrice)}${r.label ? ` <span class="te-mini">${esc(r.label)}</span>` : ""}</td>
250      <td>${esc(r.expiryDate)}</td>
251      <td>${esc(r.daysToExpiry)}</td>
252      <td>${esc(money(r.premium, 2))}</td>
253      <td>${esc(Number(r.contracts || 0).toLocaleString())}</td>
254      <td>${esc(money(r.invested))}</td>
255      <td>${esc(money(r.payoffAtTarget))}</td>
256      <td class="${r.profit >= 0 ? "pos" : "neg"}">${esc(money(r.profit))}</td>
257      <td class="${r.roi >= 0 ? "pos" : "neg"}">${esc(pct(r.roi))}</td>
258      <td>${r.impliedVolatility != null ? (Number(r.impliedVolatility) * 100).toFixed(0) + "%" : "—"}</td>
259      <td>${r.openInterest != null ? esc(Number(r.openInterest).toLocaleString()) : "—"}</td>
260    </tr>`
261      )
262      .join("");
263
264    host.querySelector("#te-results").innerHTML = `
265      <div class="te-results-block">
266        <div class="te-summary te-summary-full">
267          <div><span>Ticker</span><strong class="te-ticker">${esc(data.ticker)}</strong></div>
268          <div><span>Current Price</span><strong>${esc(money(data.currentPrice, 2))}</strong></div>
269          ${
270            data.sharesHeld
271              ? `<div><span>Shares Held</span><strong>${esc(Number(data.sharesHeld).toLocaleString())}<em>(${Math.floor(data.sharesHeld / 100)} ct max)</em></strong></div>`
272              : ""
273          }
274          ${
275            data.costBasis
276              ? `<div><span>Cost Basis</span><strong>${esc(money(data.costBasis, 2))}</strong></div>`
277              : ""
278          }
279          <div><span>Target Price</span><strong class="${moveCls}">${esc(money(data.targetPrice, 2))}</strong></div>
280          <div><span>Expected Move</span><strong class="${moveCls}">${downside ? "−" : "+"}${esc(data.expectedMove)}%</strong></div>
281          <div><span>Trade Mode</span><strong class="te-mode-${modeCls}">${esc(mLabel.toUpperCase())}</strong></div>
282          <div><span>Horizon</span><strong>${esc(data.timeHorizonDays)}d</strong></div>
283          <div><span>Budget</span><strong>${esc(money(data.budget))}</strong></div>
284          <div><span>Options Found</span><strong>${esc(o.affordable || 0)} / ${esc(o.totalFound || 0)}</strong></div>
285          ${
286            data.historicalVolatility != null
287              ? `<div><span>30d HV</span><strong>${(data.historicalVolatility * 100).toFixed(1)}%</strong></div>`
288              : ""
289          }
290          ${
291            data.pricingMethod
292              ? `<div class="te-pricing"><span>Pricing Method</span><strong>${esc(data.pricingMethod)}</strong></div>`
293              : ""
294          }
295        </div>
296        <div class="te-verdict ${recCls}">
297          <div class="te-verdict-top">
298            <span class="te-verdict-kicker">Recommendation</span>
299            <span class="te-rec-pill">${esc(recLabel)}</span>
300          </div>
301          <p>${esc(v.reasoning || "")}</p>
302        </div>
303        <p class="te-section-label">Scenario Comparison</p>
304        <div class="te-cards te-cards-3">${cards.join("")}</div>
305        ${
306          top.length
307            ? `<p class="te-section-label">All Affordable ${isSell ? "Short" : "Long"} ${isPut ? "Puts" : "Calls"} — Ranked by ROI</p>
308        <div class="te-table-wrap"><table class="te-table">
309          <thead><tr><th>Strike</th><th>Expiry</th><th>Days</th><th>Premium</th><th>Contracts</th><th>Invested</th><th>Value@Target</th><th>Profit</th><th>ROI</th><th>IV</th><th>OI</th></tr></thead>
310          <tbody>${rows}</tbody>
311        </table></div>`
312            : o.totalFound === 0
313              ? `<div class="te-empty">No ${isPut ? "put" : "call"} options found for ${esc(data.ticker)} near your horizon.</div>`
314              : ""
315        }
316        <p class="te-note">DISCLAIMER: For analytical purposes only. Premiums prefer Polygon market prints (NBBO when entitled, otherwise day VWAP/close) and only fall back to Black-Scholes when no print exists. Sell credits use the bid when NBBO is available. Not financial advice.</p>
317      </div>
318    `;
319  }
320
321  function discoveryHTML() {
322    const locked = !isPro();
323    const lockMask = locked
324      ? `<div class="tl-lock-mask"><a href="/login" class="tl-lock-pill">Pro only — Upgrade to unlock Discovery</a></div>`
325      : "";
326    return `
327<div class="te-discovery">
328  <div class="te-disc-head">
329    <p class="te-kicker">Discovery</p>
330    <h2>Ideas &amp; setups</h2>
331    <p class="te-disc-sub">Live structures, hot tickers, premium selling desk, and BTR signals — Pro unlocks the full board.</p>
332  </div>
333  <div id="te-discovery-gate" class="te-discovery-gate${locked ? " tl-lock" : ""}">
334    ${lockMask}
335    <div class="te-presets-wrap">
336      <p class="te-section-label">Quick presets</p>
337      <div class="te-presets" id="te-presets">
338        ${PRESETS.map((p, i) => `<button type="button" data-preset="${esc(p.id)}" class="${i === 0 ? "is-on" : ""}">${esc(p.label)}</button>`).join("")}
339      </div>
340    </div>
341    <section class="te-block">
342      <div class="te-block-h">
343        <h3>Today's Best Structures</h3>
344        <span class="te-faint">Sorted by priority</span>
345      </div>
346      <div class="te-struct-grid" id="te-best"></div>
347      <p class="te-empty" id="te-best-empty">Building today's best structures from live signals…</p>
348    </section>
349    <section class="te-block">
350      <h3>Hot Tickers</h3>
351      <div class="te-hot-row" id="te-hot"></div>
352      <p class="te-empty" id="te-hot-empty">Ranking hot tickers…</p>
353    </section>
354    <section class="te-block">
355      <h3>Premium Selling Desk</h3>
356      <div class="te-prem-grid" id="te-prem"></div>
357      <p class="te-empty" id="te-prem-empty">Scanning for rich-IV premium to sell…</p>
358    </section>
359    <section class="te-block">
360      <h3>From BTR Signals</h3>
361      <div class="te-btr-list" id="te-btr"></div>
362      <p class="te-empty" id="te-btr-empty">Loading BTR-linked ideas…</p>
363    </section>
364  </div>
365</div>`;
366  }
367
368  function renderShell(host) {
369    host.innerHTML = `
370<div class="te-page">
371  <div class="te-inner">
372    <div class="te-hero">
373      <p class="te-kicker">Structure desk</p>
374      <h1>Trade Engine</h1>
375      <p class="te-sub">Assume the move. We price whether shares or options win — and which contract gets you there. Discovery below is Pro.</p>
376    </div>
377    <form id="te-form" class="te-form">
378      <div class="te-modes" role="group" aria-label="Trade mode">
379        <button type="button" data-mode="buy_call" class="is-on">Buy Call</button>
380        <button type="button" data-mode="buy_put">Buy Put</button>
381        <button type="button" data-mode="sell_call">Sell Call</button>
382        <button type="button" data-mode="sell_put">Sell Put</button>
383      </div>
384      <div class="te-grid te-grid-3">
385        <label>Ticker<input name="ticker" value="AAPL" maxlength="10" required /></label>
386        <label>Expected move (%)<input name="move" value="20" required /></label>
387        <label>Horizon (days)<input name="days" value="30" required /></label>
388      </div>
389      <div class="te-grid te-grid-2 te-sizing">
390        <label>Shares held — optional<input name="shares" value="" placeholder="e.g. 1200" inputmode="numeric" /></label>
391        <label>Budget ($) — optional<input name="budget" value="" placeholder="e.g. 100000" inputmode="decimal" /></label>
392      </div>
393      <p class="te-sizing-hint">Enter shares, budget, or both. Shares set inventory (contracts ≤ floor(shares ÷ 100)); budget sets capital. With both, we use the tighter constraint.</p>
394      <div class="te-actions">
395        <button type="submit" class="te-run">Run Analysis</button>
396        <span id="te-status" class="te-status"></span>
397      </div>
398      <p id="te-error" class="te-error" hidden></p>
399      <p id="te-prefill" class="te-prefill" hidden></p>
400    </form>
401    <div id="te-results"></div>
402    ${discoveryHTML()}
403  </div>
404</div>`;
405
406    let mode = "buy_call";
407    let preset = "all";
408    let discovery = null;
409    const form = host.querySelector("#te-form");
410    const err = host.querySelector("#te-error");
411    const status = host.querySelector("#te-status");
412    const prefill = host.querySelector("#te-prefill");
413    const runBtn = host.querySelector(".te-run");
414
415    function syncMode() {
416      host.querySelectorAll("[data-mode]").forEach((b) => {
417        const id = b.getAttribute("data-mode");
418        b.classList.toggle("is-on", id === mode);
419        b.classList.toggle("is-sell", id === mode && (id === "sell_call" || id === "sell_put"));
420        b.classList.toggle("is-put", id === mode && (id === "buy_put" || id === "sell_put"));
421        b.classList.toggle("is-buy", id === mode && (id === "buy_call" || id === "buy_put"));
422      });
423      if (runBtn) runBtn.textContent = `Run Analysis — ${modeLabel(mode)} →`;
424    }
425    syncMode();
426
427    host.querySelectorAll("[data-mode]").forEach((b) => {
428      b.addEventListener("click", () => {
429        const next = b.getAttribute("data-mode") || "buy_call";
430        if (next === mode) return;
431        mode = next;
432        syncMode();
433        const hasResults = !!(host.querySelector("#te-results")?.innerHTML || "").trim();
434        if (hasResults) runAnalysis({ tradeMode: mode });
435      });
436    });
437
438    async function runAnalysis(overrides) {
439      err.hidden = true;
440      status.textContent = "Running…";
441      const fd = new FormData(form);
442      const ticker = String(overrides?.ticker ?? fd.get("ticker") ?? "")
443        .trim()
444        .toUpperCase();
445      const move = parseFloat(String(overrides?.move ?? fd.get("move") ?? "").replace(",", "."));
446      const days = parseInt(String(overrides?.days ?? fd.get("days") ?? "").replace(",", "."), 10);
447      const budgetRaw = String(
448        overrides?.budget !== undefined ? overrides.budget ?? "" : fd.get("budget") ?? ""
449      ).trim();
450      const sharesRaw = String(
451        overrides?.sharesHeld !== undefined ? overrides.sharesHeld ?? "" : fd.get("shares") ?? ""
452      ).trim();
453      const budget = budgetRaw ? parseFloat(String(budgetRaw).replace(",", ".")) : NaN;
454      const shares = sharesRaw ? parseInt(sharesRaw.replace(/[^\d]/g, ""), 10) : NaN;
455      const runMode = overrides?.tradeMode || mode;
456      const hasBudget = Number.isFinite(budget) && budget > 0;
457      const hasShares = Number.isFinite(shares) && shares > 0;
458      if (!ticker || !Number.isFinite(move) || move <= 0 || !Number.isFinite(days) || days < 1) {
459        err.textContent = "Enter a valid ticker, expected move (e.g. 7.6 or 7,6), and horizon days.";
460        err.hidden = false;
461        status.textContent = "";
462        return;
463      }
464      if (!hasBudget && !hasShares) {
465        err.textContent = "Enter shares held, budget, or both — they size the trade together.";
466        err.hidden = false;
467        status.textContent = "";
468        return;
469      }
470      mode = runMode;
471      syncMode();
472      try {
473        const body = {
474          ticker,
475          expectedMove: move,
476          timeHorizonDays: days,
477          tradeMode: runMode,
478        };
479        if (hasBudget) body.budget = budget;
480        if (hasShares) body.sharesHeld = shares;
481        const res = await fetch("/api/trade-engine", {
482          method: "POST",
483          headers: { "Content-Type": "application/json" },
484          body: JSON.stringify(body),
485        });
486        const data = await res.json();
487        if (!res.ok) throw new Error(data.error || "Calculation failed.");
488        renderResults(host, data);
489        status.textContent = "Done";
490        host.querySelector("#te-results")?.scrollIntoView({ behavior: "smooth", block: "start" });
491      } catch (ex) {
492        err.textContent = ex.message || "An error occurred.";
493        err.hidden = false;
494        status.textContent = "";
495      }
496    }
497
498    function loadIntoEngine(p) {
499      form.ticker.value = p.ticker || "";
500      form.move.value = String(p.expectedMovePct ?? 15);
501      form.days.value = String(p.timeHorizonDays ?? 30);
502      form.budget.value = p.budgetDefault != null ? String(Math.round(p.budgetDefault)) : "";
503      form.shares.value =
504        p.sharesHeld != null && p.sharesHeld > 0 ? String(Math.floor(p.sharesHeld)) : "";
505      mode = p.tradeMode || "buy_call";
506      syncMode();
507      prefill.hidden = false;
508      prefill.textContent = `Loaded ${p.ticker} from discovery — running analysis…`;
509      form.scrollIntoView({ behavior: "smooth", block: "start" });
510      runAnalysis({
511        ticker: p.ticker,
512        move: p.expectedMovePct,
513        days: p.timeHorizonDays,
514        budget: p.budgetDefault,
515        sharesHeld: p.sharesHeld,
516        tradeMode: p.tradeMode,
517      });
518    }
519
520    function syncDiscoveryGate() {
521      const pro = isPro();
522      const gate = host.querySelector("#te-discovery-gate");
523      if (!gate) return;
524      gate.classList.toggle("tl-lock", !pro);
525      let mask = gate.querySelector(":scope > .tl-lock-mask");
526      if (!pro) {
527        if (!mask) {
528          mask = document.createElement("div");
529          mask.className = "tl-lock-mask";
530          mask.innerHTML =
531            '<a href="/login" class="tl-lock-pill">Pro only — Upgrade to unlock Discovery</a>';
532          gate.appendChild(mask);
533        }
534      } else if (mask) {
535        mask.remove();
536      }
537      host.dataset.pro = String(pro);
538    }
539
540    function paintDiscovery() {
541      syncDiscoveryGate();
542      const bestEl = host.querySelector("#te-best");
543      const hotEl = host.querySelector("#te-hot");
544      const premEl = host.querySelector("#te-prem");
545      const btrEl = host.querySelector("#te-btr");
546      const bestEmpty = host.querySelector("#te-best-empty");
547      const hotEmpty = host.querySelector("#te-hot-empty");
548      const premEmpty = host.querySelector("#te-prem-empty");
549      const btrEmpty = host.querySelector("#te-btr-empty");
550
551      if (!discovery) {
552        bestEmpty.textContent = "Could not load discovery ideas.";
553        hotEmpty.textContent = "No hot tickers.";
554        premEmpty.textContent = "No premium-selling ideas.";
555        btrEmpty.textContent = "No BTR-linked ideas.";
556        return;
557      }
558
559      const best = (discovery.bestStructures || []).filter((c) => matchesPreset(c.presets, preset));
560      const hot = (discovery.hotTickers || []).filter((c) => matchesPreset(c.presets, preset));
561      const prem = (discovery.premiumSelling || []).filter((c) => matchesPreset(c.presets, preset));
562      const btr = (discovery.btrSignals || []).filter((c) => matchesPreset(c.presets, preset));
563
564      bestEl.innerHTML = best
565        .map(
566          (c) => `<div class="te-idea">
567          <span class="te-idea-label">${esc(c.label)}</span>
568          <span class="te-idea-tk">${esc(c.ticker)}</span>
569          <p class="te-idea-setup">${esc(c.setup)}</p>
570          <p class="te-idea-why">${esc(c.why)}</p>
571          ${
572            c.whenToSell
573              ? `<p class="te-idea-exit"><span>Exit — </span>${esc(c.whenToSell)}</p>`
574              : ""
575          }
576          <p class="te-idea-meta">${esc(pct(c.expectedMovePct))} / ${esc(c.timeHorizonDays)}d · ${esc(c.refreshedLabel)}</p>
577          <button type="button" class="te-load" data-load="${encodeURIComponent(
578            JSON.stringify({
579              ticker: c.ticker,
580              expectedMovePct: c.expectedMovePct,
581              timeHorizonDays: c.timeHorizonDays,
582              budgetDefault: c.budgetDefault,
583              tradeMode: c.tradeMode,
584            })
585          )}">Load into Engine</button>
586        </div>`
587        )
588        .join("");
589      bestEmpty.hidden = best.length > 0;
590      bestEmpty.textContent = "No structures match this preset right now.";
591
592      hotEl.innerHTML = hot
593        .map(
594          (c) => `<div class="te-hot">
595          <div class="te-hot-top"><span class="te-idea-tk">${esc(c.ticker)}</span><span class="te-tag">${esc(c.tag)}</span></div>
596          <p class="te-idea-meta">${esc(c.priceLabel)} · ${esc(c.ivLabel)}</p>
597          <p class="te-idea-why">${esc(c.comment)}</p>
598          <p class="te-idea-meta">${esc(c.refreshedLabel)}</p>
599          <button type="button" class="te-load is-soft" data-load="${encodeURIComponent(
600            JSON.stringify({
601              ticker: c.ticker,
602              expectedMovePct: c.expectedMovePct,
603              timeHorizonDays: c.timeHorizonDays,
604              budgetDefault: c.budgetDefault,
605              tradeMode: c.tradeMode,
606            })
607          )}">Load</button>
608        </div>`
609        )
610        .join("");
611      hotEmpty.hidden = hot.length > 0;
612      hotEmpty.textContent = "No hot tickers for this preset.";
613
614      premEl.innerHTML = prem
615        .map(
616          (c) => `<div class="te-prem">
617          <div class="te-prem-body">
618            <div class="te-hot-top"><span class="te-idea-tk">${esc(c.ticker)}</span><span class="te-idea-meta">${esc(c.spotLabel)}</span></div>
619            <p class="te-prem-strike">${esc(c.strikeExpiry)}</p>
620            <p class="te-idea-meta">Premium: ${esc(c.premiumLabel)}</p>
621            <p class="te-idea-meta">Effective entry: ${esc(c.effectiveEntryLabel)}</p>
622            <p class="te-idea-why">${esc(c.why)}</p>
623            ${
624              c.whenToSell
625                ? `<p class="te-idea-exit"><span>Manage — </span>${esc(c.whenToSell)}</p>`
626                : ""
627            }
628            <p class="te-idea-meta">${esc(c.refreshedLabel)}</p>
629          </div>
630          <button type="button" class="te-load" data-load="${encodeURIComponent(
631            JSON.stringify({
632              ticker: c.ticker,
633              expectedMovePct: c.expectedMovePct,
634              timeHorizonDays: c.timeHorizonDays,
635              budgetDefault: c.budgetDefault,
636              tradeMode: c.tradeMode || "sell_put",
637            })
638          )}">Load into Engine</button>
639        </div>`
640        )
641        .join("");
642      premEmpty.hidden = prem.length > 0;
643      premEmpty.textContent = "No premium-selling ideas for this preset.";
644
645      btrEl.innerHTML = btr
646        .map(
647          (c) => `<div class="te-btr">
648          <div class="te-btr-body">
649            <div class="te-hot-top"><span class="te-idea-tk">${esc(c.ticker)}</span><span class="te-tag is-accent">${esc(c.signalType)}</span></div>
650            <p class="te-idea-meta">Expected move ${esc(pct(c.expectedMovePct))} · ${esc(c.timeHorizonDays)}d · ${esc(c.refreshedLabel)}</p>
651            <p class="te-btr-expr"><span>Best expression — </span>${esc(c.bestExpression)}</p>
652            <p class="te-idea-why">${esc(c.why)}</p>
653            ${
654              c.whenToSell
655                ? `<p class="te-idea-exit"><span>Exit — </span>${esc(c.whenToSell)}</p>`
656                : ""
657            }
658          </div>
659          <button type="button" class="te-load" data-load="${encodeURIComponent(
660            JSON.stringify({
661              ticker: c.ticker,
662              expectedMovePct: c.expectedMovePct,
663              timeHorizonDays: c.timeHorizonDays,
664              budgetDefault: c.budgetDefault,
665              tradeMode: c.tradeMode,
666            })
667          )}">Load into Engine</button>
668        </div>`
669        )
670        .join("");
671      btrEmpty.hidden = btr.length > 0;
672      btrEmpty.textContent = "No BTR-linked ideas for this preset.";
673
674      host.querySelectorAll("[data-load]").forEach((btn) => {
675        btn.addEventListener("click", () => {
676          if (!isPro()) return;
677          try {
678            loadIntoEngine(JSON.parse(decodeURIComponent(btn.getAttribute("data-load") || "%7B%7D")));
679          } catch (_) {}
680        });
681      });
682    }
683
684    host.querySelectorAll("[data-preset]").forEach((b) => {
685      b.addEventListener("click", () => {
686        if (!isPro()) return;
687        preset = b.getAttribute("data-preset") || "all";
688        host.querySelectorAll("[data-preset]").forEach((x) => {
689          x.classList.toggle("is-on", x.getAttribute("data-preset") 
689=== preset);
690        });
691        paintDiscovery();
692      });
693    });
694
695    form.addEventListener("submit", (e) => {
696      e.preventDefault();
697      prefill.hidden = true;
698      runAnalysis();
699    });
700
701    host._teSyncPro = syncDiscoveryGate;
702    host._tePaintDiscovery = paintDiscovery;
703
704    // Lock immediately so free users never see a clear flash while discovery loads.
705    syncDiscoveryGate();
706
707    fetch("/api/trade-engine/discovery", { cache: "no-store" })
708      .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
709      .then((data) => {
710        discovery = data;
711        paintDiscovery();
712      })
713      .catch(() => {
714        discovery = null;
715        paintDiscovery();
716      });
717  }
718
719  let pageObs = null;
720
721  function watchMain(main) {
722    if (pageObs) return;
723    pageObs = new MutationObserver((mutations) => {
724      if (!isEngine()) return;
725      for (const m of mutations) {
726        for (const node of m.addedNodes) {
727          if (node.nodeType !== 1 || node.id === MOUNT_ID) continue;
728          node.style.display = "none";
729          node.setAttribute("data-tl-te-hidden", "1");
730        }
731      }
732    });
733    pageObs.observe(main, { childList: true });
734  }
735
736  function unwatchMain() {
737    if (!pageObs) return;
738    pageObs.disconnect();
739    pageObs = null;
740  }
741
742  function mount() {
743    ensureNav();
744    const main = findMain();
745    if (!main) return;
746
747    if (!isEngine()) {
748      unwatchMain();
749      showReactPage(main);
750      return;
751    }
752
753    hideReactPage(main);
754    watchMain(main);
755    let host = document.getElementById(MOUNT_ID);
756    if (!host) {
757      host = document.createElement("div");
758      host.id = MOUNT_ID;
759      main.appendChild(host);
760    }
761    const pro = isPro();
762    if (host.dataset.ready === "1") {
763      if (host.dataset.pro !== String(pro) && typeof host._teSyncPro === "function") {
764        host._teSyncPro();
765      }
766      return;
767    }
768    renderShell(host);
769    host.dataset.ready = "1";
770    host.dataset.pro = String(pro);
771  }
772
773  function onRoute() {
774    const on = isEngine();
775    const host = document.getElementById(MOUNT_ID);
776    if (on && host && host.dataset.ready === "1") return;
777    if (!on && !host) return;
778    mount();
779  }
780
781  window.addEventListener("tl:route", onRoute);
782  window.addEventListener("popstate", onRoute);
783  window.addEventListener("storage", onRoute);
784  ["pushState", "replaceState"].forEach((fn) => {
785    const orig = history[fn];
786    history[fn] = function () {
787      const r = orig.apply(this, arguments);
788      onRoute();
789      return r;
790    };
791  });
792  if (document.readyState === "loading") {
793    document.addEventListener("DOMContentLoaded", onRoute);
794  } else {
795    onRoute();
796  }
797  setInterval(onRoute, 350);
798})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.