Add Top Movers screener (mid-cap+, options-tradable)
New /movers page surfaces Yahoo Finance's predefined screeners (day_gainers, day_losers, most_actives, most_shorted_stocks) filtered to common equities with market cap >= $2B, so every listed name has a deep options chain. Per-row actions jump straight into Chain / Vol Surface / IV Spike Scanner, or pin the symbol to the Tracker watchlist. - datafetch.ts: fetchMovers(category, count) using yf.screener, post-filtered to quoteType=EQUITY and marketCap >= $2B - options.ts: GET /api/movers?category=&count= - movers.html: Tabler page with 4-tab segmented control, sortable table, summary cards, volume-vs-avg ratio highlighting hot names - All page sidebars: insert "Movers" link between Vol Surface and Scanner Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -402,3 +402,64 @@ export async function scanSymbol(symbol: string): Promise<ScanResult> {
|
|||||||
ivJumpPct, ivHv, spike, bigMove, expiry,
|
ivJumpPct, ivHv, spike, bigMove, expiry,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Top-movers screener — pulls Yahoo's predefined screeners (day_gainers,
|
||||||
|
// day_losers, most_actives, most_shorted_stocks) and filters out small caps.
|
||||||
|
// Stocks with options ~= virtually all US equities with marketCap ≥ $2B,
|
||||||
|
// so we trust the cap filter and skip per-symbol option-chain verification.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export type MoverCategory = "day_gainers" | "day_losers" | "most_actives" | "most_shorted_stocks";
|
||||||
|
|
||||||
|
export type MoverRow = {
|
||||||
|
symbol: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
change: number;
|
||||||
|
changePct: number;
|
||||||
|
volume: number;
|
||||||
|
avgVolume: number;
|
||||||
|
marketCap: number;
|
||||||
|
exchange: string;
|
||||||
|
quoteType: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MIN_MARKET_CAP = 2_000_000_000; // $2B — mid-cap floor (exclude small/micro caps)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns up to `count` movers in the given Yahoo screener category,
|
||||||
|
* filtered to common stocks (EQUITY) with marketCap ≥ $2B.
|
||||||
|
*/
|
||||||
|
export async function fetchMovers(
|
||||||
|
category: MoverCategory,
|
||||||
|
count = 50
|
||||||
|
): Promise<MoverRow[]> {
|
||||||
|
// Yahoo's screener returns up to ~250 rows; we ask for 100 and post-filter.
|
||||||
|
const res = await yf.screener({ scrIds: category, count: 100 }, undefined, { validateResult: false }) as any;
|
||||||
|
const quotes: any[] = res?.quotes ?? [];
|
||||||
|
|
||||||
|
const rows: MoverRow[] = [];
|
||||||
|
for (const q of quotes) {
|
||||||
|
if (q?.quoteType !== "EQUITY") continue;
|
||||||
|
const mc = Number(q.marketCap ?? 0);
|
||||||
|
if (!Number.isFinite(mc) || mc < MIN_MARKET_CAP) continue;
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
symbol: String(q.symbol ?? "").toUpperCase(),
|
||||||
|
name: String(q.shortName ?? q.longName ?? q.displayName ?? ""),
|
||||||
|
price: Number(q.regularMarketPrice ?? 0),
|
||||||
|
change: Number(q.regularMarketChange ?? 0),
|
||||||
|
changePct: Number(q.regularMarketChangePercent ?? 0),
|
||||||
|
volume: Number(q.regularMarketVolume ?? 0),
|
||||||
|
avgVolume: Number(q.averageDailyVolume3Month ?? 0),
|
||||||
|
marketCap: mc,
|
||||||
|
exchange: String(q.fullExchangeName ?? q.exchange ?? ""),
|
||||||
|
quoteType: String(q.quoteType ?? ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rows.length >= count) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { fetchOptionsChain, fetchExpirations, scanSymbol } from "../lib/datafetch.js";
|
import { fetchOptionsChain, fetchExpirations, scanSymbol, fetchMovers, type MoverCategory } from "../lib/datafetch.js";
|
||||||
|
|
||||||
/** How old a snapshot can be and still count for the term structure (more lenient than primary TTL). */
|
/** How old a snapshot can be and still count for the term structure (more lenient than primary TTL). */
|
||||||
const TERM_STRUCTURE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
const TERM_STRUCTURE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
||||||
@@ -550,6 +550,30 @@ optionsRouter.get("/scan", async (c) => {
|
|||||||
return c.json(ok({ count: results.length, results }));
|
return c.json(ok({ count: results.length, results }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Top-movers screener — Yahoo's day_gainers / day_losers / most_actives /
|
||||||
|
// most_shorted_stocks, filtered to mid-cap and above (marketCap ≥ $2B).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
const VALID_MOVER_CATEGORIES: MoverCategory[] = [
|
||||||
|
"day_gainers", "day_losers", "most_actives", "most_shorted_stocks",
|
||||||
|
];
|
||||||
|
|
||||||
|
optionsRouter.get("/movers", async (c) => {
|
||||||
|
const cat = (c.req.query("category") ?? "day_gainers") as MoverCategory;
|
||||||
|
if (!VALID_MOVER_CATEGORIES.includes(cat)) {
|
||||||
|
return c.json(fail(`Invalid category. Use one of: ${VALID_MOVER_CATEGORIES.join(", ")}`), 400);
|
||||||
|
}
|
||||||
|
const count = Math.min(Math.max(1, Number(c.req.query("count") ?? 50)), 100);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rows = await fetchMovers(cat, count);
|
||||||
|
return c.json(ok({ category: cat, count: rows.length, results: rows }));
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
return c.json(fail(msg), 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// PATCH /api/orders/:id { status:'open'|'closed', note?:string }
|
// PATCH /api/orders/:id { status:'open'|'closed', note?:string }
|
||||||
optionsRouter.patch("/orders/:id", async (c) => {
|
optionsRouter.patch("/orders/:id", async (c) => {
|
||||||
const id = Number(c.req.param("id"));
|
const id = Number(c.req.param("id"));
|
||||||
|
|||||||
@@ -105,6 +105,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="movers.html">
|
||||||
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M3 17l6 -6l4 4l8 -8"/>
|
||||||
|
<path d="M14 7l7 0l0 7"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="nav-link-title">Movers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="scanner.html">
|
<a class="nav-link" href="scanner.html">
|
||||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
|||||||
@@ -198,6 +198,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="movers.html">
|
||||||
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M3 17l6 -6l4 4l8 -8"/>
|
||||||
|
<path d="M14 7l7 0l0 7"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="nav-link-title">Movers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="scanner.html">
|
<a class="nav-link" href="scanner.html">
|
||||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
|||||||
359
frontend/movers.html
Normal file
359
frontend/movers.html
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Top Movers — Options Pricer</title>
|
||||||
|
<link rel="stylesheet" href="/assets/tabler.min.css" />
|
||||||
|
<link rel="stylesheet" href="/assets/tabler-vendors.min.css" />
|
||||||
|
<style>
|
||||||
|
.mono { font-family:'JetBrains Mono','Fira Code',monospace; }
|
||||||
|
[x-cloak] { display:none !important; }
|
||||||
|
.mv-table th { background:#1a1c2e; color:#8b95a7; font-size:.72rem; text-transform:uppercase; letter-spacing:.05em; border-bottom:1px solid #2d3045; cursor:pointer; user-select:none; }
|
||||||
|
.mv-table th:hover { color:#cbd3df; }
|
||||||
|
.mv-table td { border-bottom:1px solid #1e2030; color:#d0d5e0; font-size:.88rem; vertical-align:middle; }
|
||||||
|
.mv-table tbody tr:hover td { background: rgba(255,255,255,0.03); }
|
||||||
|
.mv-table tbody tr.active-row { background: rgba(0, 173, 181, 0.06); }
|
||||||
|
.cat-btn { font-weight:600; }
|
||||||
|
.cat-btn.active { background:#00adb5 !important; color:#0b1020 !important; border-color:#00adb5 !important; }
|
||||||
|
.cap-mega { color:#9d8cff; }
|
||||||
|
.cap-large { color:#4d9ef7; }
|
||||||
|
.cap-mid { color:#51cf66; }
|
||||||
|
.vol-hot { color:#ffd43b; font-weight:600; }
|
||||||
|
.truncate-name { max-width: 260px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:inline-block; vertical-align:middle; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
<div class="wrapper" x-data="moversApp()" x-init="init()">
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="navbar navbar-vertical navbar-expand-lg" data-bs-theme="dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#sidebar-menu" aria-controls="sidebar-menu" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<h1 class="navbar-brand navbar-brand-autodark">
|
||||||
|
<a href="index.html" class="d-flex align-items-center gap-2 text-decoration-none">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="text-primary" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<rect x="4" y="8" width="4" height="8" rx="1"/>
|
||||||
|
<line x1="6" y1="4" x2="6" y2="8"/>
|
||||||
|
<line x1="6" y1="16" x2="6" y2="20"/>
|
||||||
|
<rect x="16" y="6" width="4" height="10" rx="1"/>
|
||||||
|
<line x1="18" y1="2" x2="18" y2="6"/>
|
||||||
|
<line x1="18" y1="16" x2="18" y2="22"/>
|
||||||
|
</svg>
|
||||||
|
<span class="fw-bold">Options Pricer</span>
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<div class="collapse navbar-collapse" id="sidebar-menu">
|
||||||
|
<ul class="navbar-nav pt-lg-3">
|
||||||
|
<li class="nav-item"><a class="nav-link" href="index.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="6" height="5" rx="2"/><rect x="4" y="13" width="6" height="7" rx="2"/><rect x="14" y="4" width="6" height="11" rx="2"/><rect x="14" y="19" width="6" height="1" rx=".5"/></svg></span><span class="nav-link-title">Dashboard</span></a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="chain.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10l18 0"/><path d="M10 5v14"/></svg></span><span class="nav-link-title">Options Chain</span></a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="surface.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12c1.333 -4.667 2.667 -7 4 -7s2.667 2.333 4 7s2.667 7 4 7s2.667 -2.333 4 -7"/></svg></span><span class="nav-link-title">Vol Surface</span></a></li>
|
||||||
|
<li class="nav-item active"><a class="nav-link" href="movers.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17l6 -6l4 4l8 -8"/><path d="M14 7l7 0l0 7"/></svg></span><span class="nav-link-title">Movers</span></a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="scanner.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><path d="M11 8v6"/><path d="M8 11h6"/></svg></span><span class="nav-link-title">Scanner</span></a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="strategy.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19l4 -6l4 2l4 -8l4 5"/><path d="M4 4v16h16"/></svg></span><span class="nav-link-title">Strategy P/L</span></a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="positions.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7v-2a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v2"/><line x1="12" y1="12" x2="12" y2="12.01"/><path d="M3 13a20 20 0 0 0 18 0"/></svg></span><span class="nav-link-title">Positions</span></a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="tracker.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="9"/><path d="M15 12l-3 -3"/></svg></span><span class="nav-link-title">Tracker</span></a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="settings.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"/></svg></span><span class="nav-link-title">Settings</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Page -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-header d-print-none">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
<h2 class="page-title">Top Movers</h2>
|
||||||
|
<div class="text-secondary mt-1">
|
||||||
|
Yahoo Finance's live screener for the biggest market moves today, filtered to
|
||||||
|
<strong>mid-cap and above</strong> (market cap ≥ <span class="mono">$2B</span>) — small-caps
|
||||||
|
are excluded so every name listed has a deep, tradable options chain. Click any symbol to jump
|
||||||
|
straight into the chain, vol surface, or IV-spike scanner.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="btn btn-primary" @click="refresh()" :disabled="loading">
|
||||||
|
<span x-show="loading" class="spinner-border spinner-border-sm me-1"></span>
|
||||||
|
<span x-text="loading ? 'Loading…' : 'Refresh'"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-body">
|
||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
<!-- Category tabs -->
|
||||||
|
<div class="card mb-3" style="background:#161824; border:1px solid #2d3045;">
|
||||||
|
<div class="card-body py-3">
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
<template x-for="c in categories" :key="c.id">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary cat-btn"
|
||||||
|
:class="category === c.id ? 'active' : ''"
|
||||||
|
@click="setCategory(c.id)"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
<span x-text="c.label"></span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<div class="ms-auto small text-secondary">
|
||||||
|
<span x-show="lastUpdated" x-cloak>Updated <span class="mono" x-text="lastUpdatedDisplay"></span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-secondary small mt-2" x-show="error" x-cloak x-text="error" style="color:#ff6b6b !important;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Summary cards -->
|
||||||
|
<div class="row g-2 mb-3" x-show="rows.length > 0" x-cloak>
|
||||||
|
<div class="col-6 col-md-3"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">Showing</div><div class="fs-4 fw-bold" x-text="rows.length"></div></div></div>
|
||||||
|
<div class="col-6 col-md-3"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">Top mover</div><div class="fs-5 fw-bold mono" x-text="topMoverDisplay"></div></div></div>
|
||||||
|
<div class="col-6 col-md-3"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">Most volume</div><div class="fs-5 fw-bold mono" x-text="topVolumeDisplay"></div></div></div>
|
||||||
|
<div class="col-6 col-md-3"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">Largest cap</div><div class="fs-5 fw-bold mono" x-text="topCapDisplay"></div></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results table -->
|
||||||
|
<div class="card" x-show="rows.length > 0" x-cloak style="background:#161824; border:1px solid #2d3045;">
|
||||||
|
<div class="card-header d-flex align-items-center justify-content-between" style="border-bottom:1px solid #2d3045;">
|
||||||
|
<h3 class="card-title text-white mb-0">
|
||||||
|
<span x-text="currentCategoryLabel"></span>
|
||||||
|
<span class="text-secondary small fw-normal ms-2" x-text="'sorted by ' + sortBy + (sortDesc ? ' ↓' : ' ↑')"></span>
|
||||||
|
</h3>
|
||||||
|
<span class="text-secondary small">Click a column header to sort · click a symbol to open it</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm mv-table mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th @click="setSort('symbol')">Symbol</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th @click="setSort('price')" class="text-end">Price</th>
|
||||||
|
<th @click="setSort('changePct')" class="text-end">Δ %</th>
|
||||||
|
<th @click="setSort('change')" class="text-end">Δ $</th>
|
||||||
|
<th @click="setSort('volume')" class="text-end">Volume</th>
|
||||||
|
<th @click="setSort('volRatio')" class="text-end">Vol / Avg</th>
|
||||||
|
<th @click="setSort('marketCap')" class="text-end">Mkt Cap</th>
|
||||||
|
<th>Exchange</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<template x-for="r in sorted" :key="r.symbol">
|
||||||
|
<tr>
|
||||||
|
<td class="mono fw-bold">
|
||||||
|
<span x-text="r.symbol"></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-secondary small">
|
||||||
|
<span class="truncate-name" :title="r.name" x-text="r.name || '—'"></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end mono" x-text="r.price ? '$' + r.price.toFixed(2) : '—'"></td>
|
||||||
|
<td class="text-end mono fw-semibold" :class="r.changePct >= 0 ? 'text-success' : 'text-danger'"
|
||||||
|
x-text="(r.changePct >= 0 ? '+' : '') + r.changePct.toFixed(2) + '%'"></td>
|
||||||
|
<td class="text-end mono" :class="r.change >= 0 ? 'text-success' : 'text-danger'"
|
||||||
|
x-text="(r.change >= 0 ? '+' : '') + r.change.toFixed(2)"></td>
|
||||||
|
<td class="text-end mono" x-text="fmtVol(r.volume)"></td>
|
||||||
|
<td class="text-end mono" :class="volRatio(r) >= 2 ? 'vol-hot' : ''"
|
||||||
|
x-text="r.avgVolume ? (volRatio(r)).toFixed(2) + 'x' : '—'"></td>
|
||||||
|
<td class="text-end mono" :class="capClass(r.marketCap)" x-text="fmtCap(r.marketCap)"></td>
|
||||||
|
<td class="text-secondary small" x-text="shortExchange(r.exchange)"></td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary me-1" @click="goChain(r.symbol)" title="Open in Options Chain">Chain</button>
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1" @click="goSurface(r.symbol)" title="Open in Vol Surface">Surface</button>
|
||||||
|
<button class="btn btn-sm btn-outline-warning me-1" @click="goScanner(r.symbol)" title="Run IV Spike Scanner">IV</button>
|
||||||
|
<button class="btn btn-sm btn-outline-success" @click="addToWatchlist(r.symbol)" :title="watchlist.includes(r.symbol) ? 'Already in watchlist' : 'Add to Tracker watchlist'"
|
||||||
|
:disabled="watchlist.includes(r.symbol)">
|
||||||
|
<span x-text="watchlist.includes(r.symbol) ? '★' : '☆'"></span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty state -->
|
||||||
|
<div x-show="!loading && rows.length === 0" x-cloak class="card text-center py-5" style="background:#161824;border:1px solid #2d3045;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="text-secondary">No movers loaded yet</h3>
|
||||||
|
<p class="text-muted">Hit <strong>Refresh</strong> to pull the latest market data.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/assets/tabler.min.js" defer></script>
|
||||||
|
<script src="/assets/viewstate-store.js"></script>
|
||||||
|
<script src="/assets/alpine.min.js" defer></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function moversApp() {
|
||||||
|
return {
|
||||||
|
categories: [
|
||||||
|
{ id: 'day_gainers', label: 'Gainers' },
|
||||||
|
{ id: 'day_losers', label: 'Losers' },
|
||||||
|
{ id: 'most_actives', label: 'Most Active' },
|
||||||
|
{ id: 'most_shorted_stocks', label: 'Most Shorted' },
|
||||||
|
],
|
||||||
|
category: 'day_gainers',
|
||||||
|
rows: [],
|
||||||
|
watchlist: [],
|
||||||
|
loading: false,
|
||||||
|
error: '',
|
||||||
|
sortBy: 'changePct',
|
||||||
|
sortDesc: true,
|
||||||
|
lastUpdated: null,
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try { this.watchlist = JSON.parse(localStorage.getItem('optionsPricer:watchlist') || '[]'); } catch {}
|
||||||
|
const vs = ViewState.load('movers');
|
||||||
|
if (vs) {
|
||||||
|
this.category = vs.category ?? this.category;
|
||||||
|
this.rows = vs.rows ?? [];
|
||||||
|
this.sortBy = vs.sortBy ?? this.sortBy;
|
||||||
|
this.sortDesc = vs.sortDesc !== undefined ? vs.sortDesc : true;
|
||||||
|
this.lastUpdated = vs.lastUpdated ?? null;
|
||||||
|
}
|
||||||
|
// Auto-refresh on first open or if data is older than 5 min
|
||||||
|
const stale = !this.lastUpdated || (Date.now() - this.lastUpdated > 5 * 60 * 1000);
|
||||||
|
if (this.rows.length === 0 || stale) await this.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
setCategory(id) {
|
||||||
|
if (this.category === id) return;
|
||||||
|
this.category = id;
|
||||||
|
// Default sort per-category: by changePct for gainers/losers, by volume for actives/shorted
|
||||||
|
if (id === 'most_actives' || id === 'most_shorted_stocks') {
|
||||||
|
this.sortBy = 'volume'; this.sortDesc = true;
|
||||||
|
} else {
|
||||||
|
this.sortBy = 'changePct';
|
||||||
|
this.sortDesc = (id === 'day_gainers');
|
||||||
|
}
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
this.loading = true; this.error = '';
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/movers?category=' + encodeURIComponent(this.category) + '&count=50');
|
||||||
|
const d = await r.json();
|
||||||
|
if (!r.ok || !d.ok) throw new Error(d.error || ('HTTP ' + r.status));
|
||||||
|
this.rows = d.data.results || [];
|
||||||
|
this.lastUpdated = Date.now();
|
||||||
|
this._persist();
|
||||||
|
} catch (e) {
|
||||||
|
this.error = 'Failed to load: ' + e.message;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_persist() {
|
||||||
|
ViewState.save('movers', {
|
||||||
|
category: this.category, rows: this.rows,
|
||||||
|
sortBy: this.sortBy, sortDesc: this.sortDesc,
|
||||||
|
lastUpdated: this.lastUpdated,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setSort(col) {
|
||||||
|
if (this.sortBy === col) this.sortDesc = !this.sortDesc;
|
||||||
|
else { this.sortBy = col; this.sortDesc = true; }
|
||||||
|
this._persist();
|
||||||
|
},
|
||||||
|
|
||||||
|
volRatio(r) { return r.avgVolume > 0 ? r.volume / r.avgVolume : 0; },
|
||||||
|
|
||||||
|
get sorted() {
|
||||||
|
const dir = this.sortDesc ? -1 : 1;
|
||||||
|
return [...this.rows].sort((a, b) => {
|
||||||
|
let av, bv;
|
||||||
|
if (this.sortBy === 'volRatio') { av = this.volRatio(a); bv = this.volRatio(b); }
|
||||||
|
else { av = a[this.sortBy]; bv = b[this.sortBy]; }
|
||||||
|
if (typeof av === 'string') return av.localeCompare(bv) * dir;
|
||||||
|
return ((av ?? 0) - (bv ?? 0)) * dir;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get currentCategoryLabel() {
|
||||||
|
return this.categories.find(c => c.id === this.category)?.label ?? '';
|
||||||
|
},
|
||||||
|
|
||||||
|
get topMoverDisplay() {
|
||||||
|
if (this.rows.length === 0) return '—';
|
||||||
|
const r = [...this.rows].sort((a, b) => Math.abs(b.changePct) - Math.abs(a.changePct))[0];
|
||||||
|
return r.symbol + ' ' + (r.changePct >= 0 ? '+' : '') + r.changePct.toFixed(2) + '%';
|
||||||
|
},
|
||||||
|
|
||||||
|
get topVolumeDisplay() {
|
||||||
|
if (this.rows.length === 0) return '—';
|
||||||
|
const r = [...this.rows].sort((a, b) => b.volume - a.volume)[0];
|
||||||
|
return r.symbol + ' ' + this.fmtVol(r.volume);
|
||||||
|
},
|
||||||
|
|
||||||
|
get topCapDisplay() {
|
||||||
|
if (this.rows.length === 0) return '—';
|
||||||
|
const r = [...this.rows].sort((a, b) => b.marketCap - a.marketCap)[0];
|
||||||
|
return r.symbol + ' ' + this.fmtCap(r.marketCap);
|
||||||
|
},
|
||||||
|
|
||||||
|
get lastUpdatedDisplay() {
|
||||||
|
if (!this.lastUpdated) return '';
|
||||||
|
const d = new Date(this.lastUpdated);
|
||||||
|
return d.toLocaleTimeString();
|
||||||
|
},
|
||||||
|
|
||||||
|
fmtVol(v) {
|
||||||
|
if (!v) return '—';
|
||||||
|
if (v >= 1e9) return (v / 1e9).toFixed(2) + 'B';
|
||||||
|
if (v >= 1e6) return (v / 1e6).toFixed(2) + 'M';
|
||||||
|
if (v >= 1e3) return (v / 1e3).toFixed(1) + 'K';
|
||||||
|
return v.toString();
|
||||||
|
},
|
||||||
|
|
||||||
|
fmtCap(v) {
|
||||||
|
if (!v) return '—';
|
||||||
|
if (v >= 1e12) return '$' + (v / 1e12).toFixed(2) + 'T';
|
||||||
|
if (v >= 1e9) return '$' + (v / 1e9).toFixed(2) + 'B';
|
||||||
|
if (v >= 1e6) return '$' + (v / 1e6).toFixed(2) + 'M';
|
||||||
|
return '$' + v.toFixed(0);
|
||||||
|
},
|
||||||
|
|
||||||
|
capClass(v) {
|
||||||
|
if (v >= 200e9) return 'cap-mega';
|
||||||
|
if (v >= 10e9) return 'cap-large';
|
||||||
|
return 'cap-mid';
|
||||||
|
},
|
||||||
|
|
||||||
|
shortExchange(ex) {
|
||||||
|
if (!ex) return '';
|
||||||
|
return ex
|
||||||
|
.replace(/^Nasdaq.*$/, 'Nasdaq')
|
||||||
|
.replace(/^NYSE.*$/, 'NYSE')
|
||||||
|
.replace(/^NasdaqGS$/, 'Nasdaq');
|
||||||
|
},
|
||||||
|
|
||||||
|
addToWatchlist(sym) {
|
||||||
|
if (this.watchlist.includes(sym)) return;
|
||||||
|
this.watchlist = [...this.watchlist, sym];
|
||||||
|
try { localStorage.setItem('optionsPricer:watchlist', JSON.stringify(this.watchlist)); } catch {}
|
||||||
|
},
|
||||||
|
|
||||||
|
goChain(sym) { try { const v = ViewState.load('chain') || {}; v.symbol = sym; ViewState.save('chain', v); } catch {} window.location.href = '/chain.html'; },
|
||||||
|
goSurface(sym) { try { const v = ViewState.load('surface') || {}; v.symbol = sym; ViewState.save('surface', v); } catch {} window.location.href = '/surface.html'; },
|
||||||
|
goScanner(sym) { try { const v = ViewState.load('scanner') || {}; v.symbolsRaw = sym; ViewState.save('scanner', v); } catch {} window.location.href = '/scanner.html'; },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -82,6 +82,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="movers.html">
|
||||||
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M3 17l6 -6l4 4l8 -8"/>
|
||||||
|
<path d="M14 7l7 0l0 7"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="nav-link-title">Movers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="scanner.html">
|
<a class="nav-link" href="scanner.html">
|
||||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<li class="nav-item"><a class="nav-link" href="index.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="6" height="5" rx="2"/><rect x="4" y="13" width="6" height="7" rx="2"/><rect x="14" y="4" width="6" height="11" rx="2"/><rect x="14" y="19" width="6" height="1" rx=".5"/></svg></span><span class="nav-link-title">Dashboard</span></a></li>
|
<li class="nav-item"><a class="nav-link" href="index.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="6" height="5" rx="2"/><rect x="4" y="13" width="6" height="7" rx="2"/><rect x="14" y="4" width="6" height="11" rx="2"/><rect x="14" y="19" width="6" height="1" rx=".5"/></svg></span><span class="nav-link-title">Dashboard</span></a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="chain.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10l18 0"/><path d="M10 5v14"/></svg></span><span class="nav-link-title">Options Chain</span></a></li>
|
<li class="nav-item"><a class="nav-link" href="chain.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10l18 0"/><path d="M10 5v14"/></svg></span><span class="nav-link-title">Options Chain</span></a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="surface.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12c1.333 -4.667 2.667 -7 4 -7s2.667 2.333 4 7s2.667 7 4 7s2.667 -2.333 4 -7"/></svg></span><span class="nav-link-title">Vol Surface</span></a></li>
|
<li class="nav-item"><a class="nav-link" href="surface.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12c1.333 -4.667 2.667 -7 4 -7s2.667 2.333 4 7s2.667 7 4 7s2.667 -2.333 4 -7"/></svg></span><span class="nav-link-title">Vol Surface</span></a></li>
|
||||||
<li class="nav-item active"><a class="nav-link" href="scanner.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><path d="M11 8v6"/><path d="M8 11h6"/></svg></span><span class="nav-link-title">Scanner</span></a></li>
|
<li class="nav-item"><a class="nav-link" href="movers.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17l6 -6l4 4l8 -8"/><path d="M14 7l7 0l0 7"/></svg></span><span class="nav-link-title">Movers</span></a></li> <li class="nav-item active"><a class="nav-link" href="scanner.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><path d="M11 8v6"/><path d="M8 11h6"/></svg></span><span class="nav-link-title">Scanner</span></a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="strategy.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19l4 -6l4 2l4 -8l4 5"/><path d="M4 4v16h16"/></svg></span><span class="nav-link-title">Strategy P/L</span></a></li>
|
<li class="nav-item"><a class="nav-link" href="strategy.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19l4 -6l4 2l4 -8l4 5"/><path d="M4 4v16h16"/></svg></span><span class="nav-link-title">Strategy P/L</span></a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="positions.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7v-2a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v2"/><line x1="12" y1="12" x2="12" y2="12.01"/><path d="M3 13a20 20 0 0 0 18 0"/></svg></span><span class="nav-link-title">Positions</span></a></li>
|
<li class="nav-item"><a class="nav-link" href="positions.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7v-2a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v2"/><line x1="12" y1="12" x2="12" y2="12.01"/><path d="M3 13a20 20 0 0 0 18 0"/></svg></span><span class="nav-link-title">Positions</span></a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="tracker.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="9"/><path d="M15 12l-3 -3"/></svg></span><span class="nav-link-title">Tracker</span></a></li>
|
<li class="nav-item"><a class="nav-link" href="tracker.html"><span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="9"/><path d="M15 12l-3 -3"/></svg></span><span class="nav-link-title">Tracker</span></a></li>
|
||||||
|
|||||||
@@ -75,6 +75,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="movers.html">
|
||||||
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M3 17l6 -6l4 4l8 -8"/>
|
||||||
|
<path d="M14 7l7 0l0 7"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="nav-link-title">Movers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="scanner.html">
|
<a class="nav-link" href="scanner.html">
|
||||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
|||||||
@@ -89,6 +89,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="movers.html">
|
||||||
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M3 17l6 -6l4 4l8 -8"/>
|
||||||
|
<path d="M14 7l7 0l0 7"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="nav-link-title">Movers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="scanner.html">
|
<a class="nav-link" href="scanner.html">
|
||||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
|||||||
@@ -187,6 +187,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="movers.html">
|
||||||
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M3 17l6 -6l4 4l8 -8"/>
|
||||||
|
<path d="M14 7l7 0l0 7"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="nav-link-title">Movers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="scanner.html">
|
<a class="nav-link" href="scanner.html">
|
||||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
|||||||
@@ -152,6 +152,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="movers.html">
|
||||||
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M3 17l6 -6l4 4l8 -8"/>
|
||||||
|
<path d="M14 7l7 0l0 7"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="nav-link-title">Movers</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="scanner.html">
|
<a class="nav-link" href="scanner.html">
|
||||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||||
|
|||||||
Reference in New Issue
Block a user