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, "<") 34 .replace(/>/g, ">") 35 .replace(/"/g, """); 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 & 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.