恢复下单界面并排布局,品种推荐数据库缓存与 SSE 推送。

期货下单与持仓监控左右并排,推荐按资金过滤存库,后台刷新并通过 EventSource 推送。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-24 10:41:26 +08:00
parent 38a38cb51d
commit 709801305f
7 changed files with 480 additions and 161 deletions
+73 -5
View File
@@ -5,10 +5,11 @@ import json
from datetime import datetime
from typing import Any, Callable
from flask import flash, jsonify, redirect, render_template, request, url_for
from flask import flash, jsonify, redirect, render_template, request, url_for, Response, stream_with_context
from contract_specs import calc_position_metrics, get_contract_spec
from fee_specs import calc_fee_breakdown
from kline_stream import sse_format
from position_sizing import (
MODE_FIXED,
MODE_RISK,
@@ -16,7 +17,8 @@ from position_sizing import (
calc_order_tick_metrics,
normalize_sizing_mode,
)
from product_recommend import list_product_recommendations
from recommend_store import load_recommend_cache, refresh_recommend_cache
from recommend_stream import recommend_hub, start_recommend_worker
from risk.account_risk_lib import (
assert_can_open,
get_risk_status,
@@ -300,6 +302,7 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
).fetchone()["n"]
conn.commit()
sizing = get_sizing_mode(get_setting)
rec_cache = load_recommend_cache(conn)
return render_template(
"trade.html",
trading_mode=mode,
@@ -314,6 +317,8 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
sizing_mode=sizing,
sizing_mode_label="以损定仓" if sizing == MODE_RISK else "固定张数",
risk_percent=get_risk_percent(get_setting),
recommend_rows=rec_cache.get("rows") or [],
recommend_updated_at=rec_cache.get("updated_at"),
)
finally:
conn.close()
@@ -626,10 +631,61 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
@app.route("/api/recommend/list")
@login_required
def api_recommend_list():
"""只读数据库缓存,不在请求时拉行情。"""
conn = get_db()
capital = _capital(conn)
conn.close()
return jsonify({"ok": True, "capital": capital, "rows": list_product_recommendations(capital, _main_price)})
try:
payload = load_recommend_cache(conn)
return jsonify({"ok": True, **payload})
finally:
conn.close()
@app.route("/api/recommend/stream")
@login_required
def api_recommend_stream():
from queue import Empty
def generate():
q = recommend_hub.subscribe()
try:
conn = get_db()
try:
payload = load_recommend_cache(conn)
finally:
conn.close()
yield sse_format("recommend", {"ok": True, **payload})
while True:
try:
msg = q.get(timeout=25)
yield sse_format(msg["event"], msg["data"])
except Empty:
yield ": heartbeat\n\n"
finally:
recommend_hub.unsubscribe(q)
return Response(
stream_with_context(generate()),
mimetype="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
},
)
@app.route("/api/recommend/refresh", methods=["POST"])
@login_required
def api_recommend_refresh():
"""手动触发一次后台刷新(仍写入数据库)。"""
conn = get_db()
try:
init_strategy_tables(conn)
capital = _capital(conn)
rows = refresh_recommend_cache(conn, capital, _main_price)
payload = load_recommend_cache(conn)
recommend_hub.broadcast("recommend", {"ok": True, **payload})
return jsonify({"ok": True, "count": len(rows), **payload})
finally:
conn.close()
@app.route("/api/strategy/trend/preview", methods=["POST"])
@login_required
@@ -946,3 +1002,15 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
reduce_cooloff_after_journal(conn, trading_day=trading_day_label())
app._risk_review_hook = hook_review_mood
from db_conn import DB_PATH
def _init_tables(conn):
init_strategy_tables(conn)
start_recommend_worker(
db_path=DB_PATH,
get_capital_fn=_capital,
price_fn=_main_price,
init_tables_fn=_init_tables,
)