diff --git a/app.py b/app.py index 83efc23..82b0258 100644 --- a/app.py +++ b/app.py @@ -481,7 +481,75 @@ def api_symbol_search(): q = request.args.get("q", "") return jsonify(search_symbols(q)) -# —————————————— 页面路由 —————————————— + +@app.route("/api/key_prices") +@login_required +def api_key_prices(): + """关键位监控列表:批量现价与距上/下沿距离。""" + conn = get_db() + rows = conn.execute( + "SELECT id, symbol, market_code, sina_code, upper, lower " + "FROM key_monitors WHERE status='active' OR status IS NULL" + ).fetchall() + conn.close() + out = [] + for r in rows: + sym = r["symbol"] + market = r["market_code"] or "" + sina = r["sina_code"] or "" + upper = float(r["upper"]) + lower = float(r["lower"]) + price = fetch_price(sym, market, sina) + dist_upper = None + dist_lower = None + if price is not None: + dist_upper = round(upper - price, 2) + dist_lower = round(price - lower, 2) + out.append({ + "id": r["id"], + "price": price, + "dist_upper": dist_upper, + "dist_lower": dist_lower, + }) + return jsonify(out) + + +@app.route("/api/plan_prices") +@login_required +def api_plan_prices(): + """今日计划:批量现价与距决策区间上/下沿距离。""" + today = today_str() + conn = get_db() + rows = conn.execute( + "SELECT id, symbol, market_code, sina_code, zone_upper, zone_lower " + "FROM order_plans WHERE plan_date=? AND status IN ('planned', 'active')", + (today,), + ).fetchall() + conn.close() + out = [] + for r in rows: + sym = r["symbol"] + market = r["market_code"] or "" + sina = r["sina_code"] or "" + upper = float(r["zone_upper"]) + lower = float(r["zone_lower"]) + price = fetch_price(sym, market, sina) + dist_upper = None + dist_lower = None + in_zone = False + if price is not None: + dist_upper = round(upper - price, 2) + dist_lower = round(price - lower, 2) + in_zone = lower <= price <= upper + out.append({ + "id": r["id"], + "price": price, + "dist_upper": dist_upper, + "dist_lower": dist_lower, + "in_zone": in_zone, + }) + return jsonify(out) + @app.route("/") @login_required diff --git a/static/js/keys.js b/static/js/keys.js new file mode 100644 index 0000000..3ff4f3f --- /dev/null +++ b/static/js/keys.js @@ -0,0 +1,39 @@ +(function () { + var timer = null; + + function fmtDist(v) { + if (v === null || v === undefined) return '--'; + return v.toFixed(2); + } + + function pollPrices() { + var list = document.getElementById('key-monitor-list'); + if (!list || !list.querySelector('.key-item')) return; + + fetch('/api/key_prices') + .then(function (r) { return r.json(); }) + .then(function (rows) { + rows.forEach(function (row) { + var el = list.querySelector('.key-item[data-key-id="' + row.id + '"]'); + if (!el) return; + var priceEl = el.querySelector('.live-price'); + var upEl = el.querySelector('.dist-up'); + var downEl = el.querySelector('.dist-down'); + if (priceEl) { + priceEl.textContent = row.price != null ? row.price : '--'; + } + if (upEl) upEl.textContent = fmtDist(row.dist_upper); + if (downEl) downEl.textContent = fmtDist(row.dist_lower); + }); + }) + .catch(function () { /* ignore */ }); + } + + function startPolling() { + if (timer) clearInterval(timer); + pollPrices(); + timer = setInterval(pollPrices, 1000); + } + + document.addEventListener('DOMContentLoaded', startPolling); +})(); diff --git a/static/js/plans.js b/static/js/plans.js new file mode 100644 index 0000000..4021afc --- /dev/null +++ b/static/js/plans.js @@ -0,0 +1,45 @@ +(function () { + var timer = null; + + function fmtDist(v) { + if (v === null || v === undefined) return '--'; + return v.toFixed(2); + } + + function pollPrices() { + var list = document.getElementById('plan-monitor-list'); + if (!list || !list.querySelector('.plan-item')) return; + + fetch('/api/plan_prices') + .then(function (r) { return r.json(); }) + .then(function (rows) { + rows.forEach(function (row) { + var el = list.querySelector('.plan-item[data-plan-id="' + row.id + '"]'); + if (!el) return; + var priceEl = el.querySelector('.live-price'); + var distEl = el.querySelector('.live-dist'); + var upEl = el.querySelector('.dist-up'); + var downEl = el.querySelector('.dist-down'); + if (priceEl) { + priceEl.textContent = row.price != null ? row.price : '--'; + } + if (row.in_zone && distEl) { + distEl.innerHTML = '在区间内'; + } else if (distEl && upEl && downEl) { + distEl.innerHTML = + '距上 ' + fmtDist(row.dist_upper) + '' + + ' · 距下 ' + fmtDist(row.dist_lower) + ''; + } + }); + }) + .catch(function () { /* ignore */ }); + } + + function startPolling() { + if (timer) clearInterval(timer); + pollPrices(); + timer = setInterval(pollPrices, 1000); + } + + document.addEventListener('DOMContentLoaded', startPolling); +})(); diff --git a/templates/base.html b/templates/base.html index ded7514..c6acb5f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -331,7 +331,11 @@ .review-detail-image{flex-shrink:0;padding-top:.75rem;border-top:1px solid var(--table-border)} .review-detail-image img{width:100%;border-radius:10px;border:1px solid var(--card-border)} .review-detail-image .no-img{color:var(--text-muted);font-size:.85rem;padding:2rem;text-align:center;background:var(--card-inner);border-radius:10px} - .modal-close{float:right;color:var(--text-muted);cursor:pointer;font-size:1.2rem} + .key-live{display:flex;flex-direction:column;align-items:center;gap:.15rem;min-width:100px;font-size:.8rem} + .key-live .live-price{font-size:1rem;font-weight:600;color:var(--accent)} + .key-live .live-dist{color:var(--text-muted);font-size:.72rem;white-space:nowrap} + .key-live .live-dist span{color:var(--text-primary)} + .list-item.key-item{gap:.65rem} .calc-readonly{background:var(--calc-bg);color:var(--accent)} @media(max-width:1100px){ .split-grid{grid-template-columns:1fr} diff --git a/templates/keys.html b/templates/keys.html index 695ef4a..0c8a6e5 100644 --- a/templates/keys.html +++ b/templates/keys.html @@ -8,7 +8,7 @@
- + @@ -35,13 +35,17 @@
-
+
{% for k in keys %} -
+
{{ k.symbol_name or k.symbol }} {{ k.monitor_type }} {{ '多' if k.direction == 'long' else '空' }}
+
+ -- + 距上 -- · 距下 -- +
上{{ k.upper }} 下{{ k.lower }}
@@ -76,3 +80,6 @@
{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/templates/plans.html b/templates/plans.html index d32b559..aefc034 100644 --- a/templates/plans.html +++ b/templates/plans.html @@ -34,15 +34,19 @@
-
+
{% for p in plans %} -
+
{{ p.symbol_name or p.symbol }} {{ '多' if p.direction == 'long' else '空' }} {% if p.status == 'planned' %}待触发 {% else %}已激活{% endif %}
+
+ -- + 距上 -- · 距下 -- +
区间{{ p.zone_lower }}~{{ p.zone_upper }} {% if p.decision_reason %} · {{ p.decision_reason }}{% endif %} @@ -93,3 +97,6 @@
{% endblock %} +{% block extra_js %} + +{% endblock %}